diff options
author | IIMarckus <iimarckus@gmail.com> | 2018-06-02 04:27:31 -0600 |
---|---|---|
committer | IIMarckus <iimarckus@gmail.com> | 2018-06-02 04:27:31 -0600 |
commit | 5d2473f767b431910da023a6e37ad3239dcdd575 (patch) | |
tree | 6200e65d9bd7e8fa8429c62d3907f21587ab74df /de/engine | |
parent | f31a2ed69f7368d88a0a1aed5c35aa8cb34af6b3 (diff) |
Start of German translation. Needs work.multilang
Diffstat (limited to 'de/engine')
37 files changed, 25602 insertions, 0 deletions
diff --git a/de/engine/HoF_room_pc.asm b/de/engine/HoF_room_pc.asm new file mode 100755 index 00000000..805f4ec1 --- /dev/null +++ b/de/engine/HoF_room_pc.asm @@ -0,0 +1,270 @@ +HallOfFamePC: + callba AnimateHallOfFame + call ClearScreen + ld c, 100 + call DelayFrames + call DisableLCD + ld hl, vFont + ld bc, $800 / 2 + call ZeroMemory + ld hl, vChars2 + $600 + ld bc, $200 / 2 + call ZeroMemory + ld hl, vChars2 + $7e0 + ld bc, $10 + ld a, $ff + call FillMemory + coord hl, 0, 0 + call FillFourRowsWithBlack + coord hl, 0, 14 + call FillFourRowsWithBlack + ld a, %11000000 + ld [rBGP], a + call EnableLCD + ld a, $ff + call PlaySoundWaitForCurrent + ld c, BANK(Music_Credits) + ld a, MUSIC_CREDITS + call PlayMusic + ld c, 128 + call DelayFrames + xor a + ld [wUnusedCD3D], a ; not read + ld [wNumCreditsMonsDisplayed], a + jp Credits + +FadeInCreditsText: + ld hl, HoFGBPalettes + ld b, 4 +.loop + ld a, [hli] + ld [rBGP], a + ld c, 5 + call DelayFrames + dec b + jr nz, .loop + ret + +DisplayCreditsMon: + xor a + ld [H_AUTOBGTRANSFERENABLED],a + call SaveScreenTilesToBuffer1 + call FillMiddleOfScreenWithWhite + + ; display the next monster from CreditsMons + ld hl,wNumCreditsMonsDisplayed + ld c,[hl] ; how many monsters have we displayed so far? + inc [hl] + ld b,0 + ld hl,CreditsMons + add hl,bc ; go that far in the list of monsters and get the next one + ld a,[hl] + ld [wcf91],a + ld [wd0b5],a + coord hl, 8, 6 + call GetMonHeader + call LoadFrontSpriteByMonIndex + ld hl,vBGMap0 + $c + call CreditsCopyTileMapToVRAM + xor a + ld [H_AUTOBGTRANSFERENABLED],a + call LoadScreenTilesFromBuffer1 + ld hl,vBGMap0 + call CreditsCopyTileMapToVRAM + ld a,$A7 + ld [rWX],a + ld hl,vBGMap1 + call CreditsCopyTileMapToVRAM + call FillMiddleOfScreenWithWhite + ld a,%11111100 ; make the mon a black silhouette + ld [rBGP],a + +; scroll the mon left by one tile 7 times + ld bc,7 +.scrollLoop1 + call ScrollCreditsMonLeft + dec c + jr nz,.scrollLoop1 + +; scroll the mon left by one tile 20 times +; This time, we have to move the window left too in order to hide the text that +; is wrapping around to the right side of the screen. + ld c,20 +.scrollLoop2 + call ScrollCreditsMonLeft + ld a,[rWX] + sub 8 + ld [rWX],a + dec c + jr nz,.scrollLoop2 + + xor a + ld [hWY],a + ld a,%11000000 + ld [rBGP],a + ret + +INCLUDE "data/credit_mons.asm" + +ScrollCreditsMonLeft: + ld h, b + ld l, $20 + call ScrollCreditsMonLeft_SetSCX + ld h, $0 + ld l, $70 + call ScrollCreditsMonLeft_SetSCX + ld a, b + add $8 + ld b, a + ret + +ScrollCreditsMonLeft_SetSCX: + ld a, [rLY] + cp l + jr nz, ScrollCreditsMonLeft_SetSCX + ld a, h + ld [rSCX], a +.loop + ld a, [rLY] + cp h + jr z, .loop + ret + +HoFGBPalettes: + db %11000000 + db %11010000 + db %11100000 + db %11110000 + +CreditsCopyTileMapToVRAM: + ld a, l + ld [H_AUTOBGTRANSFERDEST], a + ld a, h + ld [H_AUTOBGTRANSFERDEST + 1], a + ld a, 1 + ld [H_AUTOBGTRANSFERENABLED], a + jp Delay3 + +ZeroMemory: +; zero bc bytes at hl + ld [hl], 0 + inc hl + inc hl + dec bc + ld a, b + or c + jr nz, ZeroMemory + ret + +FillFourRowsWithBlack: + ld bc, SCREEN_WIDTH * 4 + ld a, $7e + jp FillMemory + +FillMiddleOfScreenWithWhite: + coord hl, 0, 4 + ld bc, SCREEN_WIDTH * 10 + ld a, " " + jp FillMemory + +Credits: + ld de, CreditsOrder + push de +.nextCreditsScreen + pop de + coord hl, 9, 6 + push hl + call FillMiddleOfScreenWithWhite + pop hl +.nextCreditsCommand + ld a, [de] + inc de + push de + cp $ff + jr z, .fadeInTextAndShowMon + cp $fe + jr z, .showTextAndShowMon + cp $fd + jr z, .fadeInText + cp $fc + jr z, .showText + cp $fb + jr z, .showCopyrightText + cp $fa + jr z, .showTheEnd + push hl + push hl + ld hl, CreditsTextPointers + add a + ld c, a + ld b, 0 + add hl, bc + ld e, [hl] + inc hl + ld d, [hl] + ld a, [de] + inc de + ld c, a + ld b, $ff + pop hl + add hl, bc + call PlaceString + pop hl + ld bc, SCREEN_WIDTH * 2 + add hl, bc + pop de + jr .nextCreditsCommand +.fadeInTextAndShowMon + call FadeInCreditsText + ld c, 90 + jr .next1 +.showTextAndShowMon + ld c, 110 +.next1 + call DelayFrames + call DisplayCreditsMon + jr .nextCreditsScreen +.fadeInText + call FadeInCreditsText + ld c, 120 + jr .next2 +.showText + ld c, 140 +.next2 + call DelayFrames + jr .nextCreditsScreen +.showCopyrightText + push de + callba LoadCopyrightTiles + pop de + pop de + jr .nextCreditsCommand +.showTheEnd + ld c, 16 + call DelayFrames + call FillMiddleOfScreenWithWhite + pop de + ld de, TheEndGfx + ld hl, vChars2 + $600 + lb bc, BANK(TheEndGfx), (TheEndGfxEnd - TheEndGfx) / $10 + call CopyVideoData + coord hl, 7, 8 + ld de, TheEndTextString + call PlaceString + coord hl, 7, 9 + inc de + call PlaceString + jp FadeInCreditsText + +TheEndTextString: +; "T H E E N D" + db $64," ",$66," ",$68," ",$64,"@" + db $65," ",$67," ",$69," ",$65,"@" + +INCLUDE "data/credits_order.asm" + +INCLUDE "text/credits_text.asm" + +TheEndGfx: + INCBIN "gfx/theend.interleave.2bpp" +TheEndGfxEnd: diff --git a/de/engine/battle/core.asm b/de/engine/battle/core.asm new file mode 100755 index 00000000..6f0ad8b6 --- /dev/null +++ b/de/engine/battle/core.asm @@ -0,0 +1,8720 @@ +BattleCore: + +; These are move effects (second value from the Moves table in bank $E). +ResidualEffects1: +; most non-side effects + db CONVERSION_EFFECT + db HAZE_EFFECT + db SWITCH_AND_TELEPORT_EFFECT + db MIST_EFFECT + db FOCUS_ENERGY_EFFECT + db CONFUSION_EFFECT + db HEAL_EFFECT + db TRANSFORM_EFFECT + db LIGHT_SCREEN_EFFECT + db REFLECT_EFFECT + db POISON_EFFECT + db PARALYZE_EFFECT + db SUBSTITUTE_EFFECT + db MIMIC_EFFECT + db LEECH_SEED_EFFECT + db SPLASH_EFFECT + db -1 +SetDamageEffects: +; moves that do damage but not through normal calculations +; e.g., Super Fang, Psywave + db SUPER_FANG_EFFECT + db SPECIAL_DAMAGE_EFFECT + db -1 +ResidualEffects2: +; non-side effects not included in ResidualEffects1 +; stat-affecting moves, sleep-inflicting moves, and Bide +; e.g., Meditate, Bide, Hypnosis + db $01 + db ATTACK_UP1_EFFECT + db DEFENSE_UP1_EFFECT + db SPEED_UP1_EFFECT + db SPECIAL_UP1_EFFECT + db ACCURACY_UP1_EFFECT + db EVASION_UP1_EFFECT + db ATTACK_DOWN1_EFFECT + db DEFENSE_DOWN1_EFFECT + db SPEED_DOWN1_EFFECT + db SPECIAL_DOWN1_EFFECT + db ACCURACY_DOWN1_EFFECT + db EVASION_DOWN1_EFFECT + db BIDE_EFFECT + db SLEEP_EFFECT + db ATTACK_UP2_EFFECT + db DEFENSE_UP2_EFFECT + db SPEED_UP2_EFFECT + db SPECIAL_UP2_EFFECT + db ACCURACY_UP2_EFFECT + db EVASION_UP2_EFFECT + db ATTACK_DOWN2_EFFECT + db DEFENSE_DOWN2_EFFECT + db SPEED_DOWN2_EFFECT + db SPECIAL_DOWN2_EFFECT + db ACCURACY_DOWN2_EFFECT + db EVASION_DOWN2_EFFECT + db -1 +AlwaysHappenSideEffects: +; Attacks that aren't finished after they faint the opponent. + db DRAIN_HP_EFFECT + db EXPLODE_EFFECT + db DREAM_EATER_EFFECT + db PAY_DAY_EFFECT + db TWO_TO_FIVE_ATTACKS_EFFECT + db $1E + db ATTACK_TWICE_EFFECT + db RECOIL_EFFECT + db TWINEEDLE_EFFECT + db RAGE_EFFECT + db -1 +SpecialEffects: +; Effects from arrays 2, 4, and 5B, minus Twineedle and Rage. +; Includes all effects that do not need to be called at the end of +; ExecutePlayerMove (or ExecuteEnemyMove), because they have already been handled + db DRAIN_HP_EFFECT + db EXPLODE_EFFECT + db DREAM_EATER_EFFECT + db PAY_DAY_EFFECT + db SWIFT_EFFECT + db TWO_TO_FIVE_ATTACKS_EFFECT + db $1E + db CHARGE_EFFECT + db SUPER_FANG_EFFECT + db SPECIAL_DAMAGE_EFFECT + db FLY_EFFECT + db ATTACK_TWICE_EFFECT + db JUMP_KICK_EFFECT + db RECOIL_EFFECT + ; fallthrough to Next EffectsArray +SpecialEffectsCont: +; damaging moves whose effect is executed prior to damage calculation + db THRASH_PETAL_DANCE_EFFECT + db TRAPPING_EFFECT + db -1 + +SlidePlayerAndEnemySilhouettesOnScreen: + call LoadPlayerBackPic + ld a, MESSAGE_BOX ; the usual text box at the bottom of the screen + ld [wTextBoxID], a + call DisplayTextBoxID + coord hl, 1, 5 + lb bc, 3, 7 + call ClearScreenArea + call DisableLCD + call LoadFontTilePatterns + call LoadHudAndHpBarAndStatusTilePatterns + ld hl, vBGMap0 + ld bc, $400 +.clearBackgroundLoop + ld a, " " + ld [hli], a + dec bc + ld a, b + or c + jr nz, .clearBackgroundLoop +; copy the work RAM tile map to VRAM + coord hl, 0, 0 + ld de, vBGMap0 + ld b, 18 ; number of rows +.copyRowLoop + ld c, 20 ; number of columns +.copyColumnLoop + ld a, [hli] + ld [de], a + inc e + dec c + jr nz, .copyColumnLoop + ld a, 12 ; number of off screen tiles to the right of screen in VRAM + add e ; skip the off screen tiles + ld e, a + jr nc, .noCarry + inc d +.noCarry + dec b + jr nz, .copyRowLoop + call EnableLCD + ld a, $90 + ld [hWY], a + ld [rWY], a + xor a + ld [hTilesetType], a + ld [hSCY], a + dec a + ld [wUpdateSpritesEnabled], a + call Delay3 + xor a + ld [H_AUTOBGTRANSFERENABLED], a + ld b, $70 + ld c, $90 + ld a, c + ld [hSCX], a + call DelayFrame + ld a, %11100100 ; inverted palette for silhouette effect + ld [rBGP], a + ld [rOBP0], a + ld [rOBP1], a +.slideSilhouettesLoop ; slide silhouettes of the player's pic and the enemy's pic onto the screen + ld h, b + ld l, $40 + call SetScrollXForSlidingPlayerBodyLeft ; begin background scrolling on line $40 + inc b + inc b + ld h, $0 + ld l, $60 + call SetScrollXForSlidingPlayerBodyLeft ; end background scrolling on line $60 + call SlidePlayerHeadLeft + ld a, c + ld [hSCX], a + dec c + dec c + jr nz, .slideSilhouettesLoop + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + ld a, $31 + ld [hStartTileID], a + coord hl, 1, 5 + predef CopyUncompressedPicToTilemap + xor a + ld [hWY], a + ld [rWY], a + inc a + ld [H_AUTOBGTRANSFERENABLED], a + call Delay3 + ld b, SET_PAL_BATTLE + call RunPaletteCommand + call HideSprites + jpab PrintBeginningBattleText + +; when a battle is starting, silhouettes of the player's pic and the enemy's pic are slid onto the screen +; the lower of the player's pic (his body) is part of the background, but his head is a sprite +; the reason for this is that it shares Y coordinates with the lower part of the enemy pic, so background scrolling wouldn't work for both pics +; instead, the enemy pic is part of the background and uses the scroll register, while the player's head is a sprite and is slid by changing its X coordinates in a loop +SlidePlayerHeadLeft: + push bc + ld hl, wOAMBuffer + $01 + ld c, $15 ; number of OAM entries + ld de, $4 ; size of OAM entry +.loop + dec [hl] ; decrement X + dec [hl] ; decrement X + add hl, de ; next OAM entry + dec c + jr nz, .loop + pop bc + ret + +SetScrollXForSlidingPlayerBodyLeft: + ld a, [rLY] + cp l + jr nz, SetScrollXForSlidingPlayerBodyLeft + ld a, h + ld [rSCX], a +.loop + ld a, [rLY] + cp h + jr z, .loop + ret + +StartBattle: + xor a + ld [wPartyGainExpFlags], a + ld [wPartyFoughtCurrentEnemyFlags], a + ld [wActionResultOrTookBattleTurn], a + inc a + ld [wFirstMonsNotOutYet], a + ld hl, wEnemyMon1HP + ld bc, wEnemyMon2 - wEnemyMon1 - 1 + ld d, $3 +.findFirstAliveEnemyMonLoop + inc d + ld a, [hli] + or [hl] + jr nz, .foundFirstAliveEnemyMon + add hl, bc + jr .findFirstAliveEnemyMonLoop +.foundFirstAliveEnemyMon + ld a, d + ld [wSerialExchangeNybbleReceiveData], a + ld a, [wIsInBattle] + dec a ; is it a trainer battle? + call nz, EnemySendOutFirstMon ; if it is a trainer battle, send out enemy mon + ld c, 40 + call DelayFrames + call SaveScreenTilesToBuffer1 +.checkAnyPartyAlive + call AnyPartyAlive + ld a, d + and a + jp z, HandlePlayerBlackOut ; jump if no mon is alive + call LoadScreenTilesFromBuffer1 + ld a, [wBattleType] + and a ; is it a normal battle? + jp z, .playerSendOutFirstMon ; if so, send out player mon +; safari zone battle +.displaySafariZoneBattleMenu + call DisplayBattleMenu + ret c ; return if the player ran from battle + ld a, [wActionResultOrTookBattleTurn] + and a ; was the item used successfully? + jr z, .displaySafariZoneBattleMenu ; if not, display the menu again; XXX does this ever jump? + ld a, [wNumSafariBalls] + and a + jr nz, .notOutOfSafariBalls + call LoadScreenTilesFromBuffer1 + ld hl, .outOfSafariBallsText + jp PrintText +.notOutOfSafariBalls + callab PrintSafariZoneBattleText + ld a, [wEnemyMonSpeed + 1] + add a + ld b, a ; init b (which is later compared with random value) to (enemy speed % 256) * 2 + jp c, EnemyRan ; if (enemy speed % 256) > 127, the enemy runs + ld a, [wSafariBaitFactor] + and a ; is bait factor 0? + jr z, .checkEscapeFactor +; bait factor is not 0 +; divide b by 4 (making the mon less likely to run) + srl b + srl b +.checkEscapeFactor + ld a, [wSafariEscapeFactor] + and a ; is escape factor 0? + jr z, .compareWithRandomValue +; escape factor is not 0 +; multiply b by 2 (making the mon more likely to run) + sla b + jr nc, .compareWithRandomValue +; cap b at 255 + ld b, $ff +.compareWithRandomValue + call Random + cp b + jr nc, .checkAnyPartyAlive + jr EnemyRan ; if b was greater than the random value, the enemy runs + +.outOfSafariBallsText + TX_FAR _OutOfSafariBallsText + db "@" + +.playerSendOutFirstMon + xor a + ld [wWhichPokemon], a +.findFirstAliveMonLoop + call HasMonFainted + jr nz, .foundFirstAliveMon +; fainted, go to the next one + ld hl, wWhichPokemon + inc [hl] + jr .findFirstAliveMonLoop +.foundFirstAliveMon + ld a, [wWhichPokemon] + ld [wPlayerMonNumber], a + inc a + ld hl, wPartySpecies - 1 + ld c, a + ld b, 0 + add hl, bc + ld a, [hl] ; species + ld [wcf91], a + ld [wBattleMonSpecies2], a + call LoadScreenTilesFromBuffer1 + coord hl, 1, 5 + ld a, $9 + call SlideTrainerPicOffScreen + call SaveScreenTilesToBuffer1 + ld a, [wWhichPokemon] + ld c, a + ld b, FLAG_SET + push bc + ld hl, wPartyGainExpFlags + predef FlagActionPredef + ld hl, wPartyFoughtCurrentEnemyFlags + pop bc + predef FlagActionPredef + call LoadBattleMonFromParty + call LoadScreenTilesFromBuffer1 + call SendOutMon + jr MainInBattleLoop + +; wild mon or link battle enemy ran from battle +EnemyRan: + call LoadScreenTilesFromBuffer1 + ld a, [wLinkState] + cp LINK_STATE_BATTLING + ld hl, WildRanText + jr nz, .printText +; link battle + xor a + ld [wBattleResult], a + ld hl, EnemyRanText +.printText + call PrintText + ld a, SFX_RUN + call PlaySoundWaitForCurrent + xor a + ld [H_WHOSETURN], a + jpab AnimationSlideEnemyMonOff + +WildRanText: + TX_FAR _WildRanText + db "@" + +EnemyRanText: + TX_FAR _EnemyRanText + db "@" + +MainInBattleLoop: + call ReadPlayerMonCurHPAndStatus + ld hl, wBattleMonHP + ld a, [hli] + or [hl] ; is battle mon HP 0? + jp z, HandlePlayerMonFainted ; if battle mon HP is 0, jump + ld hl, wEnemyMonHP + ld a, [hli] + or [hl] ; is enemy mon HP 0? + jp z, HandleEnemyMonFainted ; if enemy mon HP is 0, jump + call SaveScreenTilesToBuffer1 + xor a + ld [wFirstMonsNotOutYet], a + ld a, [wPlayerBattleStatus2] + and (1 << NeedsToRecharge) | (1 << UsingRage) ; check if the player is using Rage or needs to recharge + jr nz, .selectEnemyMove +; the player is not using Rage and doesn't need to recharge + ld hl, wEnemyBattleStatus1 + res Flinched, [hl] ; reset flinch bit + ld hl, wPlayerBattleStatus1 + res Flinched, [hl] ; reset flinch bit + ld a, [hl] + and (1 << ThrashingAbout) | (1 << ChargingUp) ; check if the player is thrashing about or charging for an attack + jr nz, .selectEnemyMove ; if so, jump +; the player is neither thrashing about nor charging for an attack + call DisplayBattleMenu ; show battle menu + ret c ; return if player ran from battle + ld a, [wEscapedFromBattle] + and a + ret nz ; return if pokedoll was used to escape from battle + ld a, [wBattleMonStatus] + and (1 << FRZ) | SLP ; is mon frozen or asleep? + jr nz, .selectEnemyMove ; if so, jump + ld a, [wPlayerBattleStatus1] + and (1 << StoringEnergy) | (1 << UsingTrappingMove) ; check player is using Bide or using a multi-turn attack like wrap + jr nz, .selectEnemyMove ; if so, jump + ld a, [wEnemyBattleStatus1] + bit UsingTrappingMove, a ; check if enemy is using a multi-turn attack like wrap + jr z, .selectPlayerMove ; if not, jump +; enemy is using a multi-turn attack like wrap, so player is trapped and cannot execute a move + ld a, $ff + ld [wPlayerSelectedMove], a + jr .selectEnemyMove +.selectPlayerMove + ld a, [wActionResultOrTookBattleTurn] + and a ; has the player already used the turn (e.g. by using an item, trying to run or switching pokemon) + jr nz, .selectEnemyMove + ld [wMoveMenuType], a + inc a + ld [wAnimationID], a + xor a + ld [wMenuItemToSwap], a + call MoveSelectionMenu + push af + call LoadScreenTilesFromBuffer1 + call DrawHUDsAndHPBars + pop af + jr nz, MainInBattleLoop ; if the player didn't select a move, jump +.selectEnemyMove + call SelectEnemyMove + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr nz, .noLinkBattle +; link battle + ld a, [wSerialExchangeNybbleReceiveData] + cp LINKBATTLE_RUN + jp z, EnemyRan + cp LINKBATTLE_STRUGGLE + jr z, .noLinkBattle + cp LINKBATTLE_NO_ACTION + jr z, .noLinkBattle + sub 4 + jr c, .noLinkBattle +; the link battle enemy has switched mons + ld a, [wPlayerBattleStatus1] + bit UsingTrappingMove, a ; check if using multi-turn move like Wrap + jr z, .specialMoveNotUsed + ld a, [wPlayerMoveListIndex] + ld hl, wBattleMonMoves + ld c, a + ld b, 0 + add hl, bc + ld a, [hl] + cp METRONOME ; a MIRROR MOVE check is missing, might lead to a desync in link battles + ; when combined with multi-turn moves + jr nz, .specialMoveNotUsed + ld [wPlayerSelectedMove], a +.specialMoveNotUsed + callab SwitchEnemyMon +.noLinkBattle + ld a, [wPlayerSelectedMove] + cp QUICK_ATTACK + jr nz, .playerDidNotUseQuickAttack + ld a, [wEnemySelectedMove] + cp QUICK_ATTACK + jr z, .compareSpeed ; if both used Quick Attack + jp .playerMovesFirst ; if player used Quick Attack and enemy didn't +.playerDidNotUseQuickAttack + ld a, [wEnemySelectedMove] + cp QUICK_ATTACK + jr z, .enemyMovesFirst ; if enemy used Quick Attack and player didn't + ld a, [wPlayerSelectedMove] + cp COUNTER + jr nz, .playerDidNotUseCounter + ld a, [wEnemySelectedMove] + cp COUNTER + jr z, .compareSpeed ; if both used Counter + jr .enemyMovesFirst ; if player used Counter and enemy didn't +.playerDidNotUseCounter + ld a, [wEnemySelectedMove] + cp COUNTER + jr z, .playerMovesFirst ; if enemy used Counter and player didn't +.compareSpeed + ld de, wBattleMonSpeed ; player speed value + ld hl, wEnemyMonSpeed ; enemy speed value + ld c, $2 + call StringCmp ; compare speed values + jr z, .speedEqual + jr nc, .playerMovesFirst ; if player is faster + jr .enemyMovesFirst ; if enemy is faster +.speedEqual ; 50/50 chance for both players + ld a, [hSerialConnectionStatus] + cp USING_INTERNAL_CLOCK + jr z, .invertOutcome + call BattleRandom + cp $80 + jr c, .playerMovesFirst + jr .enemyMovesFirst +.invertOutcome + call BattleRandom + cp $80 + jr c, .enemyMovesFirst + jr .playerMovesFirst +.enemyMovesFirst + ld a, $1 + ld [H_WHOSETURN], a + callab TrainerAI + jr c, .AIActionUsedEnemyFirst + call ExecuteEnemyMove + ld a, [wEscapedFromBattle] + and a ; was Teleport, Road, or Whirlwind used to escape from battle? + ret nz ; if so, return + ld a, b + and a + jp z, HandlePlayerMonFainted +.AIActionUsedEnemyFirst + call HandlePoisonBurnLeechSeed + jp z, HandleEnemyMonFainted + call DrawHUDsAndHPBars + call ExecutePlayerMove + ld a, [wEscapedFromBattle] + and a ; was Teleport, Road, or Whirlwind used to escape from battle? + ret nz ; if so, return + ld a, b + and a + jp z, HandleEnemyMonFainted + call HandlePoisonBurnLeechSeed + jp z, HandlePlayerMonFainted + call DrawHUDsAndHPBars + call CheckNumAttacksLeft + jp MainInBattleLoop +.playerMovesFirst + call ExecutePlayerMove + ld a, [wEscapedFromBattle] + and a ; was Teleport, Road, or Whirlwind used to escape from battle? + ret nz ; if so, return + ld a, b + and a + jp z, HandleEnemyMonFainted + call HandlePoisonBurnLeechSeed + jp z, HandlePlayerMonFainted + call DrawHUDsAndHPBars + ld a, $1 + ld [H_WHOSETURN], a + callab TrainerAI + jr c, .AIActionUsedPlayerFirst + call ExecuteEnemyMove + ld a, [wEscapedFromBattle] + and a ; was Teleport, Road, or Whirlwind used to escape from battle? + ret nz ; if so, return + ld a, b + and a + jp z, HandlePlayerMonFainted +.AIActionUsedPlayerFirst + call HandlePoisonBurnLeechSeed + jp z, HandleEnemyMonFainted + call DrawHUDsAndHPBars + call CheckNumAttacksLeft + jp MainInBattleLoop + +HandlePoisonBurnLeechSeed: + ld hl, wBattleMonHP + ld de, wBattleMonStatus + ld a, [H_WHOSETURN] + and a + jr z, .playersTurn + ld hl, wEnemyMonHP + ld de, wEnemyMonStatus +.playersTurn + ld a, [de] + and (1 << BRN) | (1 << PSN) + jr z, .notBurnedOrPoisoned + push hl + ld hl, HurtByPoisonText + ld a, [de] + and 1 << BRN + jr z, .poisoned + ld hl, HurtByBurnText +.poisoned + call PrintText + xor a + ld [wAnimationType], a + ld a,BURN_PSN_ANIM + call PlayMoveAnimation ; play burn/poison animation + pop hl + call HandlePoisonBurnLeechSeed_DecreaseOwnHP +.notBurnedOrPoisoned + ld de, wPlayerBattleStatus2 + ld a, [H_WHOSETURN] + and a + jr z, .playersTurn2 + ld de, wEnemyBattleStatus2 +.playersTurn2 + ld a, [de] + add a + jr nc, .notLeechSeeded + push hl + ld a, [H_WHOSETURN] + push af + xor $1 + ld [H_WHOSETURN], a + xor a + ld [wAnimationType], a + ld a,ABSORB + call PlayMoveAnimation ; play leech seed animation (from opposing mon) + pop af + ld [H_WHOSETURN], a + pop hl + call HandlePoisonBurnLeechSeed_DecreaseOwnHP + call HandlePoisonBurnLeechSeed_IncreaseEnemyHP + push hl + ld hl, HurtByLeechSeedText + call PrintText + pop hl +.notLeechSeeded + ld a, [hli] + or [hl] + ret nz ; test if fainted + call DrawHUDsAndHPBars + ld c, 20 + call DelayFrames + xor a + ret + +HurtByPoisonText: + TX_FAR _HurtByPoisonText + db "@" + +HurtByBurnText: + TX_FAR _HurtByBurnText + db "@" + +HurtByLeechSeedText: + TX_FAR _HurtByLeechSeedText + db "@" + +; decreases the mon's current HP by 1/16 of the Max HP (multiplied by number of toxic ticks if active) +; note that the toxic ticks are considered even if the damage is not poison (hence the Leech Seed glitch) +; hl: HP pointer +; bc (out): total damage +HandlePoisonBurnLeechSeed_DecreaseOwnHP: + push hl + push hl + ld bc, $e ; skip to max HP + add hl, bc + ld a, [hli] ; load max HP + ld [wHPBarMaxHP+1], a + ld b, a + ld a, [hl] + ld [wHPBarMaxHP], a + ld c, a + srl b + rr c + srl b + rr c + srl c + srl c ; c = max HP/16 (assumption: HP < 1024) + ld a, c + and a + jr nz, .nonZeroDamage + inc c ; damage is at least 1 +.nonZeroDamage + ld hl, wPlayerBattleStatus3 + ld de, wPlayerToxicCounter + ld a, [H_WHOSETURN] + and a + jr z, .playersTurn + ld hl, wEnemyBattleStatus3 + ld de, wEnemyToxicCounter +.playersTurn + bit BadlyPoisoned, [hl] + jr z, .noToxic + ld a, [de] ; increment toxic counter + inc a + ld [de], a + ld hl, $0000 +.toxicTicksLoop + add hl, bc + dec a + jr nz, .toxicTicksLoop + ld b, h ; bc = damage * toxic counter + ld c, l +.noToxic + pop hl + inc hl + ld a, [hl] ; subtract total damage from current HP + ld [wHPBarOldHP], a + sub c + ld [hld], a + ld [wHPBarNewHP], a + ld a, [hl] + ld [wHPBarOldHP+1], a + sbc b + ld [hl], a + ld [wHPBarNewHP+1], a + jr nc, .noOverkill + xor a ; overkill: zero HP + ld [hli], a + ld [hl], a + ld [wHPBarNewHP], a + ld [wHPBarNewHP+1], a +.noOverkill + call UpdateCurMonHPBar + pop hl + ret + +; adds bc to enemy HP +; bc isn't updated if HP subtracted was capped to prevent overkill +HandlePoisonBurnLeechSeed_IncreaseEnemyHP: + push hl + ld hl, wEnemyMonMaxHP + ld a, [H_WHOSETURN] + and a + jr z, .playersTurn + ld hl, wBattleMonMaxHP +.playersTurn + ld a, [hli] + ld [wHPBarMaxHP+1], a + ld a, [hl] + ld [wHPBarMaxHP], a + ld de, wBattleMonHP - wBattleMonMaxHP + add hl, de ; skip back from max hp to current hp + ld a, [hl] + ld [wHPBarOldHP], a ; add bc to current HP + add c + ld [hld], a + ld [wHPBarNewHP], a + ld a, [hl] + ld [wHPBarOldHP+1], a + adc b + ld [hli], a + ld [wHPBarNewHP+1], a + ld a, [wHPBarMaxHP] + ld c, a + ld a, [hld] + sub c + ld a, [wHPBarMaxHP+1] + ld b, a + ld a, [hl] + sbc b + jr c, .noOverfullHeal + ld a, b ; overfull heal, set HP to max HP + ld [hli], a + ld [wHPBarNewHP+1], a + ld a, c + ld [hl], a + ld [wHPBarNewHP], a +.noOverfullHeal + ld a, [H_WHOSETURN] + xor $1 + ld [H_WHOSETURN], a + call UpdateCurMonHPBar + ld a, [H_WHOSETURN] + xor $1 + ld [H_WHOSETURN], a + pop hl + ret + +UpdateCurMonHPBar: + coord hl, 10, 9 ; tile pointer to player HP bar + ld a, [H_WHOSETURN] + and a + ld a, $1 + jr z, .playersTurn + coord hl, 2, 2 ; tile pointer to enemy HP bar + xor a +.playersTurn + push bc + ld [wHPBarType], a + predef UpdateHPBar2 + pop bc + ret + +CheckNumAttacksLeft: + ld a, [wPlayerNumAttacksLeft] + and a + jr nz, .checkEnemy +; player has 0 attacks left + ld hl, wPlayerBattleStatus1 + res UsingTrappingMove, [hl] ; player not using multi-turn attack like wrap any more +.checkEnemy + ld a, [wEnemyNumAttacksLeft] + and a + ret nz +; enemy has 0 attacks left + ld hl, wEnemyBattleStatus1 + res UsingTrappingMove, [hl] ; enemy not using multi-turn attack like wrap any more + ret + +HandleEnemyMonFainted: + xor a + ld [wInHandlePlayerMonFainted], a + call FaintEnemyPokemon + call AnyPartyAlive + ld a, d + and a + jp z, HandlePlayerBlackOut ; if no party mons are alive, the player blacks out + ld hl, wBattleMonHP + ld a, [hli] + or [hl] ; is battle mon HP zero? + call nz, DrawPlayerHUDAndHPBar ; if battle mon HP is not zero, draw player HD and HP bar + ld a, [wIsInBattle] + dec a + ret z ; return if it's a wild battle + call AnyEnemyPokemonAliveCheck + jp z, TrainerBattleVictory + ld hl, wBattleMonHP + ld a, [hli] + or [hl] ; does battle mon have 0 HP? + jr nz, .skipReplacingBattleMon ; if not, skip replacing battle mon + call DoUseNextMonDialogue ; this call is useless in a trainer battle. it shouldn't be here + ret c + call ChooseNextMon +.skipReplacingBattleMon + ld a, $1 + ld [wActionResultOrTookBattleTurn], a + call ReplaceFaintedEnemyMon + jp z, EnemyRan + xor a + ld [wActionResultOrTookBattleTurn], a + jp MainInBattleLoop + +FaintEnemyPokemon: + call ReadPlayerMonCurHPAndStatus + ld a, [wIsInBattle] + dec a + jr z, .wild + ld a, [wEnemyMonPartyPos] + ld hl, wEnemyMon1HP + ld bc, wEnemyMon2 - wEnemyMon1 + call AddNTimes + xor a + ld [hli], a + ld [hl], a +.wild + ld hl, wPlayerBattleStatus1 + res AttackingMultipleTimes, [hl] +; Bug. This only zeroes the high byte of the player's accumulated damage, +; setting the accumulated damage to itself mod 256 instead of 0 as was probably +; intended. That alone is problematic, but this mistake has another more severe +; effect. This function's counterpart for when the player mon faints, +; RemoveFaintedPlayerMon, zeroes both the high byte and the low byte. In a link +; battle, the other player's Game Boy will call that function in response to +; the enemy mon (the player mon from the other side's perspective) fainting, +; and the states of the two Game Boys will go out of sync unless the damage +; was congruent to 0 modulo 256. + xor a + ld [wPlayerBideAccumulatedDamage], a + ld hl, wEnemyStatsToDouble ; clear enemy statuses + ld [hli], a + ld [hli], a + ld [hli], a + ld [hli], a + ld [hl], a + ld [wEnemyDisabledMove], a + ld [wEnemyDisabledMoveNumber], a + ld [wEnemyMonMinimized], a + ld hl, wPlayerUsedMove + ld [hli], a + ld [hl], a + coord hl, 12, 5 + coord de, 12, 6 + call SlideDownFaintedMonPic + coord hl, 0, 0 + lb bc, 4, 11 + call ClearScreenArea + ld a, [wIsInBattle] + dec a + jr z, .wild_win + xor a + ld [wFrequencyModifier], a + ld [wTempoModifier], a + ld a, SFX_FAINT_FALL + call PlaySoundWaitForCurrent +.sfxwait + ld a, [wChannelSoundIDs + Ch4] + cp SFX_FAINT_FALL + jr z, .sfxwait + ld a, SFX_FAINT_THUD + call PlaySound + call WaitForSoundToFinish + jr .sfxplayed +.wild_win + call EndLowHealthAlarm + ld a, MUSIC_DEFEATED_WILD_MON + call PlayBattleVictoryMusic +.sfxplayed +; bug: win sfx is played for wild battles before checking for player mon HP +; this can lead to odd scenarios where both player and enemy faint, as the win sfx plays yet the player never won the battle + ld hl, wBattleMonHP + ld a, [hli] + or [hl] + jr nz, .playermonnotfaint + ld a, [wInHandlePlayerMonFainted] + and a ; was this called by HandlePlayerMonFainted? + jr nz, .playermonnotfaint ; if so, don't call RemoveFaintedPlayerMon twice + call RemoveFaintedPlayerMon +.playermonnotfaint + call AnyPartyAlive + ld a, d + and a + ret z + ld hl, EnemyMonFaintedText + call PrintText + call PrintEmptyString + call SaveScreenTilesToBuffer1 + xor a + ld [wBattleResult], a + ld b, EXP_ALL + call IsItemInBag + push af + jr z, .giveExpToMonsThatFought ; if no exp all, then jump + +; the player has exp all +; first, we halve the values that determine exp gain +; the enemy mon base stats are added to stat exp, so they are halved +; the base exp (which determines normal exp) is also halved + ld hl, wEnemyMonBaseStats + ld b, $7 +.halveExpDataLoop + srl [hl] + inc hl + dec b + jr nz, .halveExpDataLoop + +; give exp (divided evenly) to the mons that actually fought in battle against the enemy mon that has fainted +; if exp all is in the bag, this will be only be half of the stat exp and normal exp, due to the above loop +.giveExpToMonsThatFought + xor a + ld [wBoostExpByExpAll], a + callab GainExperience + pop af + ret z ; return if no exp all + +; the player has exp all +; now, set the gain exp flag for every party member +; half of the total stat exp and normal exp will divided evenly amongst every party member + ld a, $1 + ld [wBoostExpByExpAll], a + ld a, [wPartyCount] + ld b, 0 +.gainExpFlagsLoop + scf + rl b + dec a + jr nz, .gainExpFlagsLoop + ld a, b + ld [wPartyGainExpFlags], a + jpab GainExperience + +EnemyMonFaintedText: + TX_FAR _EnemyMonFaintedText + db "@" + +EndLowHealthAlarm: +; This function is called when the player has the won the battle. It turns off +; the low health alarm and prevents it from reactivating until the next battle. + xor a + ld [wLowHealthAlarm], a ; turn off low health alarm + ld [wChannelSoundIDs + Ch4], a + inc a + ld [wLowHealthAlarmDisabled], a ; prevent it from reactivating + ret + +AnyEnemyPokemonAliveCheck: + ld a, [wEnemyPartyCount] + ld b, a + xor a + ld hl, wEnemyMon1HP + ld de, wEnemyMon2 - wEnemyMon1 +.nextPokemon + or [hl] + inc hl + or [hl] + dec hl + add hl, de + dec b + jr nz, .nextPokemon + and a + ret + +; stores whether enemy ran in Z flag +ReplaceFaintedEnemyMon: + ld hl, wEnemyHPBarColor + ld e, $30 + call GetBattleHealthBarColor + callab DrawEnemyPokeballs + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr nz, .notLinkBattle +; link battle + call LinkBattleExchangeData + ld a, [wSerialExchangeNybbleReceiveData] + cp LINKBATTLE_RUN + ret z + call LoadScreenTilesFromBuffer1 +.notLinkBattle + call EnemySendOut + xor a + ld [wEnemyMoveNum], a + ld [wActionResultOrTookBattleTurn], a + ld [wAILayer2Encouragement], a + inc a ; reset Z flag + ret + +TrainerBattleVictory: + call EndLowHealthAlarm + ld b, MUSIC_DEFEATED_GYM_LEADER + ld a, [wGymLeaderNo] + and a + jr nz, .gymleader + ld b, MUSIC_DEFEATED_TRAINER +.gymleader + ld a, [wTrainerClass] + cp SONY3 ; final battle against rival + jr nz, .notrival + ld b, MUSIC_DEFEATED_GYM_LEADER + ld hl, wFlags_D733 + set 1, [hl] +.notrival + ld a, [wLinkState] + cp LINK_STATE_BATTLING + ld a, b + call nz, PlayBattleVictoryMusic + ld hl, TrainerDefeatedText + call PrintText + ld a, [wLinkState] + cp LINK_STATE_BATTLING + ret z + call ScrollTrainerPicAfterBattle + ld c, 40 + call DelayFrames + call PrintEndBattleText +; win money + ld hl, MoneyForWinningText + call PrintText + ld de, wPlayerMoney + 2 + ld hl, wAmountMoneyWon + 2 + ld c, $3 + predef_jump AddBCDPredef + +MoneyForWinningText: + TX_FAR _MoneyForWinningText + db "@" + +TrainerDefeatedText: + TX_FAR _TrainerDefeatedText + db "@" + +PlayBattleVictoryMusic: + push af + ld a, $ff + ld [wNewSoundID], a + call PlaySoundWaitForCurrent + ld c, BANK(Music_DefeatedTrainer) + pop af + call PlayMusic + jp Delay3 + +HandlePlayerMonFainted: + ld a, 1 + ld [wInHandlePlayerMonFainted], a + call RemoveFaintedPlayerMon + call AnyPartyAlive ; test if any more mons are alive + ld a, d + and a + jp z, HandlePlayerBlackOut + ld hl, wEnemyMonHP + ld a, [hli] + or [hl] ; is enemy mon's HP 0? + jr nz, .doUseNextMonDialogue ; if not, jump +; the enemy mon has 0 HP + call FaintEnemyPokemon + ld a, [wIsInBattle] + dec a + ret z ; if wild encounter, battle is over + call AnyEnemyPokemonAliveCheck + jp z, TrainerBattleVictory +.doUseNextMonDialogue + call DoUseNextMonDialogue + ret c ; return if the player ran from battle + call ChooseNextMon + jp nz, MainInBattleLoop ; if the enemy mon has more than 0 HP, go back to battle loop +; the enemy mon has 0 HP + ld a, $1 + ld [wActionResultOrTookBattleTurn], a + call ReplaceFaintedEnemyMon + jp z, EnemyRan ; if enemy ran from battle rather than sending out another mon, jump + xor a + ld [wActionResultOrTookBattleTurn], a + jp MainInBattleLoop + +; resets flags, slides mon's pic down, plays cry, and prints fainted message +RemoveFaintedPlayerMon: + ld a, [wPlayerMonNumber] + ld c, a + ld hl, wPartyGainExpFlags + ld b, FLAG_RESET + predef FlagActionPredef ; clear gain exp flag for fainted mon + ld hl, wEnemyBattleStatus1 + res 2, [hl] ; reset "attacking multiple times" flag + ld a, [wLowHealthAlarm] + bit 7, a ; skip sound flag (red bar (?)) + jr z, .skipWaitForSound + ld a, $ff + ld [wLowHealthAlarm], a ;disable low health alarm + call WaitForSoundToFinish +.skipWaitForSound +; a is 0, so this zeroes the enemy's accumulated damage. + ld hl, wEnemyBideAccumulatedDamage + ld [hli], a + ld [hl], a + ld [wBattleMonStatus], a + call ReadPlayerMonCurHPAndStatus + coord hl, 9, 7 + lb bc, 5, 11 + call ClearScreenArea + coord hl, 1, 10 + coord de, 1, 11 + call SlideDownFaintedMonPic + ld a, $1 + ld [wBattleResult], a + +; When the player mon and enemy mon faint at the same time and the fact that the +; enemy mon has fainted is detected first (e.g. when the player mon knocks out +; the enemy mon using a move with recoil and faints due to the recoil), don't +; play the player mon's cry or show the "[player mon] fainted!" message. + ld a, [wInHandlePlayerMonFainted] + and a ; was this called by HandleEnemyMonFainted? + ret z ; if so, return + + ld a, [wBattleMonSpecies] + call PlayCry + ld hl, PlayerMonFaintedText + jp PrintText + +PlayerMonFaintedText: + TX_FAR _PlayerMonFaintedText + db "@" + +; asks if you want to use next mon +; stores whether you ran in C flag +DoUseNextMonDialogue: + call PrintEmptyString + call SaveScreenTilesToBuffer1 + ld a, [wIsInBattle] + and a + dec a + ret nz ; return if it's a trainer battle + ld hl, UseNextMonText + call PrintText +.displayYesNoBox + coord hl, 13, 9 + lb bc, 10, 14 + ld a, TWO_OPTION_MENU + ld [wTextBoxID], a + call DisplayTextBoxID + ld a, [wMenuExitMethod] + cp CHOSE_SECOND_ITEM ; did the player choose NO? + jr z, .tryRunning ; if the player chose NO, try running + and a ; reset carry + ret +.tryRunning + ld a, [wCurrentMenuItem] + and a + jr z, .displayYesNoBox ; xxx when does this happen? + ld hl, wPartyMon1Speed + ld de, wEnemyMonSpeed + jp TryRunningFromBattle + +UseNextMonText: + TX_FAR _UseNextMonText + db "@" + +; choose next player mon to send out +; stores whether enemy mon has no HP left in Z flag +ChooseNextMon: + ld a, BATTLE_PARTY_MENU + ld [wPartyMenuTypeOrMessageID], a + call DisplayPartyMenu +.checkIfMonChosen + jr nc, .monChosen +.goBackToPartyMenu + call GoBackToPartyMenu + jr .checkIfMonChosen +.monChosen + call HasMonFainted + jr z, .goBackToPartyMenu ; if mon fainted, you have to choose another + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr nz, .notLinkBattle + inc a + ld [wActionResultOrTookBattleTurn], a + call LinkBattleExchangeData +.notLinkBattle + xor a + ld [wActionResultOrTookBattleTurn], a + call ClearSprites + ld a, [wWhichPokemon] + ld [wPlayerMonNumber], a + ld c, a + ld hl, wPartyGainExpFlags + ld b, FLAG_SET + push bc + predef FlagActionPredef + pop bc + ld hl, wPartyFoughtCurrentEnemyFlags + predef FlagActionPredef + call LoadBattleMonFromParty + call GBPalWhiteOut + call LoadHudTilePatterns + call LoadScreenTilesFromBuffer1 + call RunDefaultPaletteCommand + call GBPalNormal + call SendOutMon + ld hl, wEnemyMonHP + ld a, [hli] + or [hl] + ret + +; called when player is out of usable mons. +; prints appropriate lose message, sets carry flag if player blacked out (special case for initial rival fight) +HandlePlayerBlackOut: + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr z, .notSony1Battle + ld a, [wCurOpponent] + cp OPP_SONY1 + jr nz, .notSony1Battle + coord hl, 0, 0 ; sony 1 battle + lb bc, 8, 21 + call ClearScreenArea + call ScrollTrainerPicAfterBattle + ld c, 40 + call DelayFrames + ld hl, Sony1WinText + call PrintText + ld a, [wCurMap] + cp OAKS_LAB + ret z ; starter battle in oak's lab: don't black out +.notSony1Battle + ld b, SET_PAL_BATTLE_BLACK + call RunPaletteCommand + ld hl, PlayerBlackedOutText2 + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr nz, .noLinkBattle + ld hl, LinkBattleLostText +.noLinkBattle + call PrintText + ld a, [wd732] + res 5, a + ld [wd732], a + call ClearScreen + scf + ret + +Sony1WinText: + TX_FAR _Sony1WinText + db "@" + +PlayerBlackedOutText2: + TX_FAR _PlayerBlackedOutText2 + db "@" + +LinkBattleLostText: + TX_FAR _LinkBattleLostText + db "@" + +; slides pic of fainted mon downwards until it disappears +; bug: when this is called, [H_AUTOBGTRANSFERENABLED] is non-zero, so there is screen tearing +SlideDownFaintedMonPic: + ld a, [wd730] + push af + set 6, a + ld [wd730], a + ld b, 7 ; number of times to slide +.slideStepLoop ; each iteration, the mon is slid down one row + push bc + push de + push hl + ld b, 6 ; number of rows +.rowLoop + push bc + push hl + push de + ld bc, $7 + call CopyData + pop de + pop hl + ld bc, -SCREEN_WIDTH + add hl, bc + push hl + ld h, d + ld l, e + add hl, bc + ld d, h + ld e, l + pop hl + pop bc + dec b + jr nz, .rowLoop + ld bc, SCREEN_WIDTH + add hl, bc + ld de, SevenSpacesText + call PlaceString + ld c, 2 + call DelayFrames + pop hl + pop de + pop bc + dec b + jr nz, .slideStepLoop + pop af + ld [wd730], a + ret + +SevenSpacesText: + db " @" + +; slides the player or enemy trainer off screen +; a is the number of tiles to slide it horizontally (always 9 for the player trainer or 8 for the enemy trainer) +; if a is 8, the slide is to the right, else it is to the left +; bug: when this is called, [H_AUTOBGTRANSFERENABLED] is non-zero, so there is screen tearing +SlideTrainerPicOffScreen: + ld [hSlideAmount], a + ld c, a +.slideStepLoop ; each iteration, the trainer pic is slid one tile left/right + push bc + push hl + ld b, 7 ; number of rows +.rowLoop + push hl + ld a, [hSlideAmount] + ld c, a +.columnLoop + ld a, [hSlideAmount] + cp 8 + jr z, .slideRight +.slideLeft ; slide player sprite off screen + ld a, [hld] + ld [hli], a + inc hl + jr .nextColumn +.slideRight ; slide enemy trainer sprite off screen + ld a, [hli] + ld [hld], a + dec hl +.nextColumn + dec c + jr nz, .columnLoop + pop hl + ld de, 20 + add hl, de + dec b + jr nz, .rowLoop + ld c, 2 + call DelayFrames + pop hl + pop bc + dec c + jr nz, .slideStepLoop + ret + +; send out a trainer's mon +EnemySendOut: + ld hl,wPartyGainExpFlags + xor a + ld [hl],a + ld a,[wPlayerMonNumber] + ld c,a + ld b,FLAG_SET + push bc + predef FlagActionPredef + ld hl,wPartyFoughtCurrentEnemyFlags + xor a + ld [hl],a + pop bc + predef FlagActionPredef + +; don't change wPartyGainExpFlags or wPartyFoughtCurrentEnemyFlags +EnemySendOutFirstMon: + xor a + ld hl,wEnemyStatsToDouble ; clear enemy statuses + ld [hli],a + ld [hli],a + ld [hli],a + ld [hli],a + ld [hl],a + ld [wEnemyDisabledMove],a + ld [wEnemyDisabledMoveNumber],a + ld [wEnemyMonMinimized],a + ld hl,wPlayerUsedMove + ld [hli],a + ld [hl],a + dec a + ld [wAICount],a + ld hl,wPlayerBattleStatus1 + res 5,[hl] + coord hl, 18, 0 + ld a,8 + call SlideTrainerPicOffScreen + call PrintEmptyString + call SaveScreenTilesToBuffer1 + ld a,[wLinkState] + cp LINK_STATE_BATTLING + jr nz,.next + ld a,[wSerialExchangeNybbleReceiveData] + sub 4 + ld [wWhichPokemon],a + jr .next3 +.next + ld b,$FF +.next2 + inc b + ld a,[wEnemyMonPartyPos] + cp b + jr z,.next2 + ld hl,wEnemyMon1 + ld a,b + ld [wWhichPokemon],a + push bc + ld bc,wEnemyMon2 - wEnemyMon1 + call AddNTimes + pop bc + inc hl + ld a,[hli] + ld c,a + ld a,[hl] + or c + jr z,.next2 +.next3 + ld a,[wWhichPokemon] + ld hl,wEnemyMon1Level + ld bc,wEnemyMon2 - wEnemyMon1 + call AddNTimes + ld a,[hl] + ld [wCurEnemyLVL],a + ld a,[wWhichPokemon] + inc a + ld hl,wEnemyPartyCount + ld c,a + ld b,0 + add hl,bc + ld a,[hl] + ld [wEnemyMonSpecies2],a + ld [wcf91],a + call LoadEnemyMonData + ld hl,wEnemyMonHP + ld a,[hli] + ld [wLastSwitchInEnemyMonHP],a + ld a,[hl] + ld [wLastSwitchInEnemyMonHP + 1],a + ld a,1 + ld [wCurrentMenuItem],a + ld a,[wFirstMonsNotOutYet] + dec a + jr z,.next4 + ld a,[wPartyCount] + dec a + jr z,.next4 + ld a,[wLinkState] + cp LINK_STATE_BATTLING + jr z,.next4 + ld a,[wOptions] + bit 6,a + jr nz,.next4 + ld hl, TrainerAboutToUseText + call PrintText + coord hl, 0, 7 + lb bc, 8, 1 + ld a,TWO_OPTION_MENU + ld [wTextBoxID],a + call DisplayTextBoxID + ld a,[wCurrentMenuItem] + and a + jr nz,.next4 + ld a,BATTLE_PARTY_MENU + ld [wPartyMenuTypeOrMessageID],a + call DisplayPartyMenu +.next9 + ld a,1 + ld [wCurrentMenuItem],a + jr c,.next7 + ld hl,wPlayerMonNumber + ld a,[wWhichPokemon] + cp [hl] + jr nz,.next6 + ld hl,AlreadyOutText + call PrintText +.next8 + call GoBackToPartyMenu + jr .next9 +.next6 + call HasMonFainted + jr z,.next8 + xor a + ld [wCurrentMenuItem],a +.next7 + call GBPalWhiteOut + call LoadHudTilePatterns + call LoadScreenTilesFromBuffer1 +.next4 + call ClearSprites + coord hl, 0, 0 + lb bc, 4, 11 + call ClearScreenArea + ld b, SET_PAL_BATTLE + call RunPaletteCommand + call GBPalNormal + ld hl,TrainerSentOutText + call PrintText + ld a,[wEnemyMonSpecies2] + ld [wcf91],a + ld [wd0b5],a + call GetMonHeader + ld de,vFrontPic + call LoadMonFrontSprite + ld a,-$31 + ld [hStartTileID],a + coord hl, 15, 6 + predef AnimateSendingOutMon + ld a,[wEnemyMonSpecies2] + call PlayCry + call DrawEnemyHUDAndHPBar + ld a,[wCurrentMenuItem] + and a + ret nz + xor a + ld [wPartyGainExpFlags],a + ld [wPartyFoughtCurrentEnemyFlags],a + call SaveScreenTilesToBuffer1 + jp SwitchPlayerMon + +TrainerAboutToUseText: + TX_FAR _TrainerAboutToUseText + db "@" + +TrainerSentOutText: + TX_FAR _TrainerSentOutText + db "@" + +; tests if the player has any pokemon that are not fainted +; sets d = 0 if all fainted, d != 0 if some mons are still alive +AnyPartyAlive: + ld a, [wPartyCount] + ld e, a + xor a + ld hl, wPartyMon1HP + ld bc, wPartyMon2 - wPartyMon1 - 1 +.partyMonsLoop + or [hl] + inc hl + or [hl] + add hl, bc + dec e + jr nz, .partyMonsLoop + ld d, a + ret + +; tests if player mon has fainted +; stores whether mon has fainted in Z flag +HasMonFainted: + ld a, [wWhichPokemon] + ld hl, wPartyMon1HP + ld bc, wPartyMon2 - wPartyMon1 + call AddNTimes + ld a, [hli] + or [hl] + ret nz + ld a, [wFirstMonsNotOutYet] + and a + jr nz, .done + ld hl, NoWillText + call PrintText +.done + xor a + ret + +NoWillText: + TX_FAR _NoWillText + db "@" + +; try to run from battle (hl = player speed, de = enemy speed) +; stores whether the attempt was successful in carry flag +TryRunningFromBattle: + call IsGhostBattle + jp z, .canEscape ; jump if it's a ghost battle + ld a, [wBattleType] + cp BATTLE_TYPE_SAFARI + jp z, .canEscape ; jump if it's a safari battle + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jp z, .canEscape + ld a, [wIsInBattle] + dec a + jr nz, .trainerBattle ; jump if it's a trainer battle + ld a, [wNumRunAttempts] + inc a + ld [wNumRunAttempts], a + ld a, [hli] + ld [H_MULTIPLICAND + 1], a + ld a, [hl] + ld [H_MULTIPLICAND + 2], a + ld a, [de] + ld [hEnemySpeed], a + inc de + ld a, [de] + ld [hEnemySpeed + 1], a + call LoadScreenTilesFromBuffer1 + ld de, H_MULTIPLICAND + 1 + ld hl, hEnemySpeed + ld c, 2 + call StringCmp + jr nc, .canEscape ; jump if player speed greater than enemy speed + xor a + ld [H_MULTIPLICAND], a + ld a, 32 + ld [H_MULTIPLIER], a + call Multiply ; multiply player speed by 32 + ld a, [H_PRODUCT + 2] + ld [H_DIVIDEND], a + ld a, [H_PRODUCT + 3] + ld [H_DIVIDEND + 1], a + ld a, [hEnemySpeed] + ld b, a + ld a, [hEnemySpeed + 1] +; divide enemy speed by 4 + srl b + rr a + srl b + rr a + and a + jr z, .canEscape ; jump if enemy speed divided by 4, mod 256 is 0 + ld [H_DIVISOR], a ; ((enemy speed / 4) % 256) + ld b, $2 + call Divide ; divide (player speed * 32) by ((enemy speed / 4) % 256) + ld a, [H_QUOTIENT + 2] + and a ; is the quotient greater than 256? + jr nz, .canEscape ; if so, the player can escape + ld a, [wNumRunAttempts] + ld c, a +; add 30 to the quotient for each run attempt +.loop + dec c + jr z, .compareWithRandomValue + ld b, 30 + ld a, [H_QUOTIENT + 3] + add b + ld [H_QUOTIENT + 3], a + jr c, .canEscape + jr .loop +.compareWithRandomValue + call BattleRandom + ld b, a + ld a, [H_QUOTIENT + 3] + cp b + jr nc, .canEscape ; if the random value was less than or equal to the quotient + ; plus 30 times the number of attempts, the player can escape +; can't escape + ld a, $1 + ld [wActionResultOrTookBattleTurn], a ; you lose your turn when you can't escape + ld hl, CantEscapeText + jr .printCantEscapeOrNoRunningText +.trainerBattle + ld hl, NoRunningText +.printCantEscapeOrNoRunningText + call PrintText + ld a, 1 + ld [wForcePlayerToChooseMon], a + call SaveScreenTilesToBuffer1 + and a ; reset carry + ret +.canEscape + ld a, [wLinkState] + cp LINK_STATE_BATTLING + ld a, $2 + jr nz, .playSound +; link battle + call SaveScreenTilesToBuffer1 + xor a + ld [wActionResultOrTookBattleTurn], a + ld a, LINKBATTLE_RUN + ld [wPlayerMoveListIndex], a + call LinkBattleExchangeData + call LoadScreenTilesFromBuffer1 + ld a, [wSerialExchangeNybbleReceiveData] + cp LINKBATTLE_RUN + ld a, $2 + jr z, .playSound + dec a +.playSound + ld [wBattleResult], a + ld a, SFX_RUN + call PlaySoundWaitForCurrent + ld hl, GotAwayText + call PrintText + call WaitForSoundToFinish + call SaveScreenTilesToBuffer1 + scf ; set carry + ret + +CantEscapeText: + TX_FAR _CantEscapeText + db "@" + +NoRunningText: + TX_FAR _NoRunningText + db "@" + +GotAwayText: + TX_FAR _GotAwayText + db "@" + +; copies from party data to battle mon data when sending out a new player mon +LoadBattleMonFromParty: + ld a, [wWhichPokemon] + ld bc, wPartyMon2 - wPartyMon1 + ld hl, wPartyMon1Species + call AddNTimes + ld de, wBattleMonSpecies + ld bc, wBattleMonDVs - wBattleMonSpecies + call CopyData + ld bc, wPartyMon1DVs - wPartyMon1OTID + add hl, bc + ld de, wBattleMonDVs + ld bc, NUM_DVS + call CopyData + ld de, wBattleMonPP + ld bc, NUM_MOVES + call CopyData + ld de, wBattleMonLevel + ld bc, wBattleMonPP - wBattleMonLevel + call CopyData + ld a, [wBattleMonSpecies2] + ld [wd0b5], a + call GetMonHeader + ld hl, wPartyMonNicks + ld a, [wPlayerMonNumber] + call SkipFixedLengthTextEntries + ld de, wBattleMonNick + ld bc, NAME_LENGTH + call CopyData + ld hl, wBattleMonLevel + ld de, wPlayerMonUnmodifiedLevel ; block of memory used for unmodified stats + ld bc, 1 + NUM_STATS * 2 + call CopyData + call ApplyBurnAndParalysisPenaltiesToPlayer + call ApplyBadgeStatBoosts + ld a, $7 ; default stat modifier + ld b, NUM_STAT_MODS + ld hl, wPlayerMonAttackMod +.statModLoop + ld [hli], a + dec b + jr nz, .statModLoop + ret + +; copies from enemy party data to current enemy mon data when sending out a new enemy mon +LoadEnemyMonFromParty: + ld a, [wWhichPokemon] + ld bc, wEnemyMon2 - wEnemyMon1 + ld hl, wEnemyMons + call AddNTimes + ld de, wEnemyMonSpecies + ld bc, wEnemyMonDVs - wEnemyMonSpecies + call CopyData + ld bc, wEnemyMon1DVs - wEnemyMon1OTID + add hl, bc + ld de, wEnemyMonDVs + ld bc, NUM_DVS + call CopyData + ld de, wEnemyMonPP + ld bc, NUM_MOVES + call CopyData + ld de, wEnemyMonLevel + ld bc, wEnemyMonPP - wEnemyMonLevel + call CopyData + ld a, [wEnemyMonSpecies] + ld [wd0b5], a + call GetMonHeader + ld hl, wEnemyMonNicks + ld a, [wWhichPokemon] + call SkipFixedLengthTextEntries + ld de, wEnemyMonNick + ld bc, NAME_LENGTH + call CopyData + ld hl, wEnemyMonLevel + ld de, wEnemyMonUnmodifiedLevel ; block of memory used for unmodified stats + ld bc, 1 + NUM_STATS * 2 + call CopyData + call ApplyBurnAndParalysisPenaltiesToEnemy + ld hl, wMonHBaseStats + ld de, wEnemyMonBaseStats + ld b, NUM_STATS +.copyBaseStatsLoop + ld a, [hli] + ld [de], a + inc de + dec b + jr nz, .copyBaseStatsLoop + ld a, $7 ; default stat modifier + ld b, NUM_STAT_MODS + ld hl, wEnemyMonStatMods +.statModLoop + ld [hli], a + dec b + jr nz, .statModLoop + ld a, [wWhichPokemon] + ld [wEnemyMonPartyPos], a + ret + +SendOutMon: + callab PrintSendOutMonMessage + ld hl, wEnemyMonHP + ld a, [hli] + or [hl] ; is enemy mon HP zero? + jp z, .skipDrawingEnemyHUDAndHPBar; if HP is zero, skip drawing the HUD and HP bar + call DrawEnemyHUDAndHPBar +.skipDrawingEnemyHUDAndHPBar + call DrawPlayerHUDAndHPBar + predef LoadMonBackPic + xor a + ld [hStartTileID], a + ld hl, wBattleAndStartSavedMenuItem + ld [hli], a + ld [hl], a + ld [wBoostExpByExpAll], a + ld [wDamageMultipliers], a + ld [wPlayerMoveNum], a + ld hl, wPlayerUsedMove + ld [hli], a + ld [hl], a + ld hl, wPlayerStatsToDouble + ld [hli], a + ld [hli], a + ld [hli], a + ld [hli], a + ld [hl], a + ld [wPlayerDisabledMove], a + ld [wPlayerDisabledMoveNumber], a + ld [wPlayerMonMinimized], a + ld b, SET_PAL_BATTLE + call RunPaletteCommand + ld hl, wEnemyBattleStatus1 + res UsingTrappingMove, [hl] + ld a, $1 + ld [H_WHOSETURN], a + ld a, POOF_ANIM + call PlayMoveAnimation + coord hl, 4, 11 + predef AnimateSendingOutMon + ld a, [wcf91] + call PlayCry + call PrintEmptyString + jp SaveScreenTilesToBuffer1 + +; show 2 stages of the player mon getting smaller before disappearing +AnimateRetreatingPlayerMon: + coord hl, 1, 5 + lb bc, 7, 7 + call ClearScreenArea + coord hl, 3, 7 + lb bc, 5, 5 + xor a + ld [wDownscaledMonSize], a + ld [hBaseTileID], a + predef CopyDownscaledMonTiles + ld c, 4 + call DelayFrames + call .clearScreenArea + coord hl, 4, 9 + lb bc, 3, 3 + ld a, 1 + ld [wDownscaledMonSize], a + xor a + ld [hBaseTileID], a + predef CopyDownscaledMonTiles + call Delay3 + call .clearScreenArea + ld a, $4c + Coorda 5, 11 +.clearScreenArea + coord hl, 1, 5 + lb bc, 7, 7 + jp ClearScreenArea + +; reads player's current mon's HP into wBattleMonHP +ReadPlayerMonCurHPAndStatus: + ld a, [wPlayerMonNumber] + ld hl, wPartyMon1HP + ld bc, wPartyMon2 - wPartyMon1 + call AddNTimes + ld d, h + ld e, l + ld hl, wBattleMonHP + ld bc, $4 ; 2 bytes HP, 1 byte unknown (unused?), 1 byte status + jp CopyData + +DrawHUDsAndHPBars: + call DrawPlayerHUDAndHPBar + jp DrawEnemyHUDAndHPBar + +DrawPlayerHUDAndHPBar: + xor a + ld [H_AUTOBGTRANSFERENABLED], a + coord hl, 9, 7 + lb bc, 5, 11 + call ClearScreenArea + callab PlacePlayerHUDTiles + coord hl, 18, 9 + ld [hl], $73 + ld de, wBattleMonNick + coord hl, 10, 7 + call CenterMonName + call PlaceString + ld hl, wBattleMonSpecies + ld de, wLoadedMon + ld bc, wBattleMonDVs - wBattleMonSpecies + call CopyData + ld hl, wBattleMonLevel + ld de, wLoadedMonLevel + ld bc, wBattleMonPP - wBattleMonLevel + call CopyData + coord hl, 14, 8 + push hl + inc hl + ld de, wLoadedMonStatus + call PrintStatusConditionNotFainted + pop hl + jr nz, .doNotPrintLevel + call PrintLevel +.doNotPrintLevel + ld a, [wLoadedMonSpecies] + ld [wcf91], a + coord hl, 10, 9 + predef DrawHP + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + ld hl, wPlayerHPBarColor + call GetBattleHealthBarColor + ld hl, wBattleMonHP + ld a, [hli] + or [hl] + jr z, .fainted + ld a, [wLowHealthAlarmDisabled] + and a ; has the alarm been disabled because the player has already won? + ret nz ; if so, return + ld a, [wPlayerHPBarColor] + cp HP_BAR_RED + jr z, .setLowHealthAlarm +.fainted + ld hl, wLowHealthAlarm + bit 7, [hl] ;low health alarm enabled? + ld [hl], $0 + ret z + xor a + ld [wChannelSoundIDs + Ch4], a + ret +.setLowHealthAlarm + ld hl, wLowHealthAlarm + set 7, [hl] ;enable low health alarm + ret + +DrawEnemyHUDAndHPBar: + xor a + ld [H_AUTOBGTRANSFERENABLED], a + coord hl, 0, 0 + lb bc, 4, 12 + call ClearScreenArea + callab PlaceEnemyHUDTiles + ld de, wEnemyMonNick + coord hl, 1, 0 + call CenterMonName + call PlaceString + coord hl, 4, 1 + push hl + inc hl + ld de, wEnemyMonStatus + call PrintStatusConditionNotFainted + pop hl + jr nz, .skipPrintLevel ; if the mon has a status condition, skip printing the level + ld a, [wEnemyMonLevel] + ld [wLoadedMonLevel], a + call PrintLevel +.skipPrintLevel + ld hl, wEnemyMonHP + ld a, [hli] + ld [H_MULTIPLICAND + 1], a + ld a, [hld] + ld [H_MULTIPLICAND + 2], a + or [hl] ; is current HP zero? + jr nz, .hpNonzero +; current HP is 0 +; set variables for DrawHPBar + ld c, a + ld e, a + ld d, $6 + jp .drawHPBar +.hpNonzero + xor a + ld [H_MULTIPLICAND], a + ld a, 48 + ld [H_MULTIPLIER], a + call Multiply ; multiply current HP by 48 + ld hl, wEnemyMonMaxHP + ld a, [hli] + ld b, a + ld a, [hl] + ld [H_DIVISOR], a + ld a, b + and a ; is max HP > 255? + jr z, .doDivide +; if max HP > 255, scale both (current HP * 48) and max HP by dividing by 4 so that max HP fits in one byte +; (it needs to be one byte so it can be used as the divisor for the Divide function) + ld a, [H_DIVISOR] + srl b + rr a + srl b + rr a + ld [H_DIVISOR], a + ld a, [H_PRODUCT + 2] + ld b, a + srl b + ld a, [H_PRODUCT + 3] + rr a + srl b + rr a + ld [H_PRODUCT + 3], a + ld a, b + ld [H_PRODUCT + 2], a +.doDivide + ld a, [H_PRODUCT + 2] + ld [H_DIVIDEND], a + ld a, [H_PRODUCT + 3] + ld [H_DIVIDEND + 1], a + ld a, $2 + ld b, a + call Divide ; divide (current HP * 48) by max HP + ld a, [H_QUOTIENT + 3] +; set variables for DrawHPBar + ld e, a + ld a, $6 + ld d, a + ld c, a +.drawHPBar + xor a + ld [wHPBarType], a + coord hl, 2, 2 + call DrawHPBar + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + ld hl, wEnemyHPBarColor + +GetBattleHealthBarColor: + ld b, [hl] + call GetHealthBarColor + ld a, [hl] + cp b + ret z + ld b, SET_PAL_BATTLE + jp RunPaletteCommand + +; center's mon's name on the battle screen +; if the name is 1 or 2 letters long, it is printed 2 spaces more to the right than usual +; (i.e. for names longer than 4 letters) +; if the name is 3 or 4 letters long, it is printed 1 space more to the right than usual +; (i.e. for names longer than 4 letters) +CenterMonName: + push de + inc hl + inc hl + ld b, $2 +.loop + inc de + ld a, [de] + cp "@" + jr z, .done + inc de + ld a, [de] + cp "@" + jr z, .done + dec hl + dec b + jr nz, .loop +.done + pop de + ret + +DisplayBattleMenu: + call LoadScreenTilesFromBuffer1 ; restore saved screen + ld a, [wBattleType] + and a + jr nz, .nonstandardbattle + call DrawHUDsAndHPBars + call PrintEmptyString + call SaveScreenTilesToBuffer1 +.nonstandardbattle + ld a, [wBattleType] + cp BATTLE_TYPE_SAFARI + ld a, BATTLE_MENU_TEMPLATE + jr nz, .menuselected + ld a, SAFARI_BATTLE_MENU_TEMPLATE +.menuselected + ld [wTextBoxID], a + call DisplayTextBoxID + ld a, [wBattleType] + dec a + jp nz, .handleBattleMenuInput ; handle menu input if it's not the old man tutorial +; the following happens for the old man tutorial + ld hl, wPlayerName + ld de, wGrassRate + ld bc, NAME_LENGTH + call CopyData ; temporarily save the player name in unused space, + ; which is supposed to get overwritten when entering a + ; map with wild Pokémon. Due to an oversight, the data + ; may not get overwritten (cinnabar) and the infamous + ; Missingno. glitch can show up. + ld hl, .oldManName + ld de, wPlayerName + ld bc, NAME_LENGTH + call CopyData +; the following simulates the keystrokes by drawing menus on screen + coord hl, 7, 14 + ld [hl], "▶" + ld c, 80 + call DelayFrames + ld [hl], " " + coord hl, 7, 16 + ld [hl], "▶" + ld c, 50 + call DelayFrames + ld [hl], "▷" + ld a, $2 ; select the "ITEM" menu + jp .upperLeftMenuItemWasNotSelected +.oldManName + db "GREIS@" +.handleBattleMenuInput + ld a, [wBattleAndStartSavedMenuItem] + ld [wCurrentMenuItem], a + ld [wLastMenuItem], a + sub 2 ; check if the cursor is in the left column + jr c, .leftColumn +; cursor is in the right column + ld [wCurrentMenuItem], a + ld [wLastMenuItem], a + jr .rightColumn +.leftColumn ; put cursor in left column of menu + ld a, [wBattleType] + cp BATTLE_TYPE_SAFARI + ld a, " " + jr z, .safariLeftColumn +; put cursor in left column for normal battle menu (i.e. when it's not a Safari battle) + Coorda 12, 14 ; clear upper cursor position in right column + Coorda 12, 16 ; clear lower cursor position in right column + ld b, $7 ; top menu item X + jr .leftColumn_WaitForInput +.safariLeftColumn + Coorda 12, 14 + Coorda 12, 16 + coord hl, 7, 14 + ld de, wNumSafariBalls + lb bc, 1, 2 + call PrintNumber + ld b, $1 ; top menu item X +.leftColumn_WaitForInput + ld hl, wTopMenuItemY + ld a, $e + ld [hli], a ; wTopMenuItemY + ld a, b + ld [hli], a ; wTopMenuItemX + inc hl + inc hl + ld a, $1 + ld [hli], a ; wMaxMenuItem + ld [hl], D_RIGHT | A_BUTTON ; wMenuWatchedKeys + call HandleMenuInput + bit 4, a ; check if right was pressed + jr nz, .rightColumn + jr .AButtonPressed ; the A button was pressed +.rightColumn ; put cursor in right column of menu + ld a, [wBattleType] + cp BATTLE_TYPE_SAFARI + ld a, " " + jr z, .safariRightColumn +; put cursor in right column for normal battle menu (i.e. when it's not a Safari battle) + Coorda 7, 14 ; clear upper cursor position in left column + Coorda 7, 16 ; clear lower cursor position in left column + ld b, $c ; top menu item X + jr .rightColumn_WaitForInput +.safariRightColumn + Coorda 1, 14 ; clear upper cursor position in left column + Coorda 1, 16 ; clear lower cursor position in left column + coord hl, 7, 14 + ld de, wNumSafariBalls + lb bc, 1, 2 + call PrintNumber + ld b, $c ; top menu item X +.rightColumn_WaitForInput + ld hl, wTopMenuItemY + ld a, $e + ld [hli], a ; wTopMenuItemY + ld a, b + ld [hli], a ; wTopMenuItemX + inc hl + inc hl + ld a, $1 + ld [hli], a ; wMaxMenuItem + ld a, D_LEFT | A_BUTTON + ld [hli], a ; wMenuWatchedKeys + call HandleMenuInput + bit 5, a ; check if left was pressed + jr nz, .leftColumn ; if left was pressed, jump + ld a, [wCurrentMenuItem] + add $2 ; if we're in the right column, the actual id is +2 + ld [wCurrentMenuItem], a +.AButtonPressed + call PlaceUnfilledArrowMenuCursor + ld a, [wBattleType] + cp BATTLE_TYPE_SAFARI + ld a, [wCurrentMenuItem] + ld [wBattleAndStartSavedMenuItem], a + jr z, .handleMenuSelection +; not Safari battle +; swap the IDs of the item menu and party menu (this is probably because they swapped the positions +; of these menu items in first generation English versions) + cp $1 ; was the item menu selected? + jr nz, .notItemMenu +; item menu was selected + inc a ; increment a to 2 + jr .handleMenuSelection +.notItemMenu + cp $2 ; was the party menu selected? + jr nz, .handleMenuSelection +; party menu selected + dec a ; decrement a to 1 +.handleMenuSelection + and a + jr nz, .upperLeftMenuItemWasNotSelected +; the upper left menu item was selected + ld a, [wBattleType] + cp BATTLE_TYPE_SAFARI + jr z, .throwSafariBallWasSelected +; the "FIGHT" menu was selected + xor a + ld [wNumRunAttempts], a + jp LoadScreenTilesFromBuffer1 ; restore saved screen and return +.throwSafariBallWasSelected + ld a, SAFARI_BALL + ld [wcf91], a + jr UseBagItem + +.upperLeftMenuItemWasNotSelected ; a menu item other than the upper left item was selected + cp $2 + jp nz, PartyMenuOrRockOrRun + +; either the bag (normal battle) or bait (safari battle) was selected + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr nz, .notLinkBattle + +; can't use items in link battles + ld hl, ItemsCantBeUsedHereText + call PrintText + jp DisplayBattleMenu + +.notLinkBattle + call SaveScreenTilesToBuffer2 + ld a, [wBattleType] + cp BATTLE_TYPE_SAFARI + jr nz, BagWasSelected + +; bait was selected + ld a, SAFARI_BAIT + ld [wcf91], a + jr UseBagItem + +BagWasSelected: + call LoadScreenTilesFromBuffer1 + ld a, [wBattleType] + and a ; is it a normal battle? + jr nz, .next + +; normal battle + call DrawHUDsAndHPBars +.next + ld a, [wBattleType] + dec a ; is it the old man tutorial? + jr nz, DisplayPlayerBag ; no, it is a normal battle + ld hl, OldManItemList + ld a, l + ld [wListPointer], a + ld a, h + ld [wListPointer + 1], a + jr DisplayBagMenu + +OldManItemList: + db 1 ; # items + db POKE_BALL, 50 + db -1 + +DisplayPlayerBag: + ; get the pointer to player's bag when in a normal battle + ld hl, wNumBagItems + ld a, l + ld [wListPointer], a + ld a, h + ld [wListPointer + 1], a + +DisplayBagMenu: + xor a + ld [wPrintItemPrices], a + ld a, ITEMLISTMENU + ld [wListMenuID], a + ld a, [wBagSavedMenuItem] + ld [wCurrentMenuItem], a + call DisplayListMenuID + ld a, [wCurrentMenuItem] + ld [wBagSavedMenuItem], a + ld a, $0 + ld [wMenuWatchMovingOutOfBounds], a + ld [wMenuItemToSwap], a + jp c, DisplayBattleMenu ; go back to battle menu if an item was not selected + +UseBagItem: + ; either use an item from the bag or use a safari zone item + ld a, [wcf91] + ld [wd11e], a + call GetItemName + call CopyStringToCF50 ; copy name + xor a + ld [wPseudoItemID], a + call UseItem + call LoadHudTilePatterns + call ClearSprites + xor a + ld [wCurrentMenuItem], a + ld a, [wBattleType] + cp BATTLE_TYPE_SAFARI + jr z, .checkIfMonCaptured + + ld a, [wActionResultOrTookBattleTurn] + and a ; was the item used successfully? + jp z, BagWasSelected ; if not, go back to the bag menu + + ld a, [wPlayerBattleStatus1] + bit UsingTrappingMove, a ; is the player using a multi-turn move like wrap? + jr z, .checkIfMonCaptured + ld hl, wPlayerNumAttacksLeft + dec [hl] + jr nz, .checkIfMonCaptured + ld hl, wPlayerBattleStatus1 + res UsingTrappingMove, [hl] ; not using multi-turn move any more + +.checkIfMonCaptured + ld a, [wCapturedMonSpecies] + and a ; was the enemy mon captured with a ball? + jr nz, .returnAfterCapturingMon + + ld a, [wBattleType] + cp BATTLE_TYPE_SAFARI + jr z, .returnAfterUsingItem_NoCapture +; not a safari battle + call LoadScreenTilesFromBuffer1 + call DrawHUDsAndHPBars + call Delay3 +.returnAfterUsingItem_NoCapture + + call GBPalNormal + and a ; reset carry + ret + +.returnAfterCapturingMon + call GBPalNormal + xor a + ld [wCapturedMonSpecies], a + ld a, $2 + ld [wBattleResult], a + scf ; set carry + ret + +ItemsCantBeUsedHereText: + TX_FAR _ItemsCantBeUsedHereText + db "@" + +PartyMenuOrRockOrRun: + dec a ; was Run selected? + jp nz, BattleMenu_RunWasSelected +; party menu or rock was selected + call SaveScreenTilesToBuffer2 + ld a, [wBattleType] + cp BATTLE_TYPE_SAFARI + jr nz, .partyMenuWasSelected +; safari battle + ld a, SAFARI_ROCK + ld [wcf91], a + jp UseBagItem +.partyMenuWasSelected + call LoadScreenTilesFromBuffer1 + xor a ; NORMAL_PARTY_MENU + ld [wPartyMenuTypeOrMessageID], a + ld [wMenuItemToSwap], a + call DisplayPartyMenu +.checkIfPartyMonWasSelected + jp nc, .partyMonWasSelected ; if a party mon was selected, jump, else we quit the party menu +.quitPartyMenu + call ClearSprites + call GBPalWhiteOut + call LoadHudTilePatterns + call LoadScreenTilesFromBuffer2 + call RunDefaultPaletteCommand + call GBPalNormal + jp DisplayBattleMenu +.partyMonDeselected + coord hl, 11, 11 + ld bc, 6 * SCREEN_WIDTH + 9 + ld a, " " + call FillMemory + xor a ; NORMAL_PARTY_MENU + ld [wPartyMenuTypeOrMessageID], a + call GoBackToPartyMenu + jr .checkIfPartyMonWasSelected +.partyMonWasSelected + ld a, SWITCH_STATS_CANCEL_MENU_TEMPLATE + ld [wTextBoxID], a + call DisplayTextBoxID + ld hl, wTopMenuItemY + ld a, $c + ld [hli], a ; wTopMenuItemY + ld [hli], a ; wTopMenuItemX + xor a + ld [hli], a ; wCurrentMenuItem + inc hl + ld a, $2 + ld [hli], a ; wMaxMenuItem + ld a, B_BUTTON | A_BUTTON + ld [hli], a ; wMenuWatchedKeys + xor a + ld [hl], a ; wLastMenuItem + call HandleMenuInput + bit 1, a ; was A pressed? + jr nz, .partyMonDeselected ; if B was pressed, jump +; A was pressed + call PlaceUnfilledArrowMenuCursor + ld a, [wCurrentMenuItem] + cp $2 ; was Cancel selected? + jr z, .quitPartyMenu ; if so, quit the party menu entirely + and a ; was Switch selected? + jr z, .switchMon ; if so, jump +; Stats was selected + xor a ; PLAYER_PARTY_DATA + ld [wMonDataLocation], a + ld hl, wPartyMon1 + call ClearSprites +; display the two status screens + predef StatusScreen + predef StatusScreen2 +; now we need to reload the enemy mon pic + ld a, [wEnemyBattleStatus2] + bit HasSubstituteUp, a ; does the enemy mon have a substitute? + ld hl, AnimationSubstitute + jr nz, .doEnemyMonAnimation +; enemy mon doesn't have substitute + ld a, [wEnemyMonMinimized] + and a ; has the enemy mon used Minimise? + ld hl, AnimationMinimizeMon + jr nz, .doEnemyMonAnimation +; enemy mon is not minimised + ld a, [wEnemyMonSpecies] + ld [wcf91], a + ld [wd0b5], a + call GetMonHeader + ld de, vFrontPic + call LoadMonFrontSprite + jr .enemyMonPicReloaded +.doEnemyMonAnimation + ld b, BANK(AnimationSubstitute) ; BANK(AnimationMinimizeMon) + call Bankswitch +.enemyMonPicReloaded ; enemy mon pic has been reloaded, so return to the party menu + jp .partyMenuWasSelected +.switchMon + ld a, [wPlayerMonNumber] + ld d, a + ld a, [wWhichPokemon] + cp d ; check if the mon to switch to is already out + jr nz, .notAlreadyOut +; mon is already out + ld hl, AlreadyOutText + call PrintText + jp .partyMonDeselected +.notAlreadyOut + call HasMonFainted + jp z, .partyMonDeselected ; can't switch to fainted mon + ld a, $1 + ld [wActionResultOrTookBattleTurn], a + call GBPalWhiteOut + call ClearSprites + call LoadHudTilePatterns + call LoadScreenTilesFromBuffer1 + call RunDefaultPaletteCommand + call GBPalNormal +; fall through to SwitchPlayerMon + +SwitchPlayerMon: + callab RetreatMon + ld c, 50 + call DelayFrames + call AnimateRetreatingPlayerMon + ld a, [wWhichPokemon] + ld [wPlayerMonNumber], a + ld c, a + ld b, FLAG_SET + push bc + ld hl, wPartyGainExpFlags + predef FlagActionPredef + pop bc + ld hl, wPartyFoughtCurrentEnemyFlags + predef FlagActionPredef + call LoadBattleMonFromParty + call SendOutMon + call SaveScreenTilesToBuffer1 + ld a, $2 + ld [wCurrentMenuItem], a + and a + ret + +AlreadyOutText: + TX_FAR _AlreadyOutText + db "@" + +BattleMenu_RunWasSelected: + call LoadScreenTilesFromBuffer1 + ld a, $3 + ld [wCurrentMenuItem], a + ld hl, wBattleMonSpeed + ld de, wEnemyMonSpeed + call TryRunningFromBattle + ld a, 0 + ld [wForcePlayerToChooseMon], a + ret c + ld a, [wActionResultOrTookBattleTurn] + and a + ret nz ; return if the player couldn't escape + jp DisplayBattleMenu + +MoveSelectionMenu: + ld a, [wMoveMenuType] + dec a + jr z, .mimicmenu + dec a + jr z, .relearnmenu + jr .regularmenu + +.loadmoves + ld de, wMoves + ld bc, NUM_MOVES + call CopyData + callab FormatMovesString + ret + +.writemoves + ld de, wMovesString + ld a, [hFlags_0xFFF6] + set 2, a + ld [hFlags_0xFFF6], a + call PlaceString + ld a, [hFlags_0xFFF6] + res 2, a + ld [hFlags_0xFFF6], a + ret + +.regularmenu + call AnyMoveToSelect + ret z + ld hl, wBattleMonMoves + call .loadmoves + coord hl, 4, 12 + ld b, 4 + ld c, 14 + di ; out of pure coincidence, it is possible for vblank to occur between the di and ei + ; so it is necessary to put the di ei block to not cause tearing + call TextBoxBorder + coord hl, 4, 12 + ld [hl], $7a + coord hl, 10, 12 + ld [hl], $7e + ei + coord hl, 6, 13 + call .writemoves + ld b, $5 + ld a, $c + jr .menuset +.mimicmenu + ld hl, wEnemyMonMoves + call .loadmoves + coord hl, 0, 7 + ld b, 4 + ld c, 14 + call TextBoxBorder + coord hl, 2, 8 + call .writemoves + ld b, $1 + ld a, $7 + jr .menuset +.relearnmenu + ld a, [wWhichPokemon] + ld hl, wPartyMon1Moves + ld bc, wPartyMon2 - wPartyMon1 + call AddNTimes + call .loadmoves + coord hl, 4, 7 + ld b, 4 + ld c, 14 + call TextBoxBorder + coord hl, 6, 8 + call .writemoves + ld b, $5 + ld a, $7 +.menuset + ld hl, wTopMenuItemY + ld [hli], a ; wTopMenuItemY + ld a, b + ld [hli], a ; wTopMenuItemX + ld a, [wMoveMenuType] + cp $1 + jr z, .selectedmoveknown + ld a, $1 + jr nc, .selectedmoveknown + ld a, [wPlayerMoveListIndex] + inc a +.selectedmoveknown + ld [hli], a ; wCurrentMenuItem + inc hl ; wTileBehindCursor untouched + ld a, [wNumMovesMinusOne] + inc a + inc a + ld [hli], a ; wMaxMenuItem + ld a, [wMoveMenuType] + dec a + ld b, D_UP | D_DOWN | A_BUTTON + jr z, .matchedkeyspicked + dec a + ld b, D_UP | D_DOWN | A_BUTTON | B_BUTTON + jr z, .matchedkeyspicked + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr z, .matchedkeyspicked + ld a, [wFlags_D733] + bit BIT_TEST_BATTLE, a + ld b, D_UP | D_DOWN | A_BUTTON | B_BUTTON | SELECT + jr z, .matchedkeyspicked + ld b, $ff +.matchedkeyspicked + ld a, b + ld [hli], a ; wMenuWatchedKeys + ld a, [wMoveMenuType] + cp $1 + jr z, .movelistindex1 + ld a, [wPlayerMoveListIndex] + inc a +.movelistindex1 + ld [hl], a +; fallthrough + +SelectMenuItem: + ld a, [wMoveMenuType] + and a + jr z, .battleselect + dec a + jr nz, .select + coord hl, 1, 14 + ld de, WhichTechniqueString + call PlaceString + jr .select +.battleselect + ld a, [wFlags_D733] + bit BIT_TEST_BATTLE, a + jr nz, .select + call PrintMenuItem + ld a, [wMenuItemToSwap] + and a + jr z, .select + coord hl, 5, 13 + dec a + ld bc, SCREEN_WIDTH + call AddNTimes + ld [hl], "▷" +.select + ld hl, hFlags_0xFFF6 + set 1, [hl] + call HandleMenuInput + ld hl, hFlags_0xFFF6 + res 1, [hl] + bit 6, a + jp nz, SelectMenuItem_CursorUp ; up + bit 7, a + jp nz, SelectMenuItem_CursorDown ; down + bit 2, a + jp nz, SwapMovesInMenu ; select + bit 1, a ; B, but was it reset above? + push af + xor a + ld [wMenuItemToSwap], a + ld a, [wCurrentMenuItem] + dec a + ld [wCurrentMenuItem], a + ld b, a + ld a, [wMoveMenuType] + dec a ; if not mimic + jr nz, .notB + pop af + ret +.notB + dec a + ld a, b + ld [wPlayerMoveListIndex], a + jr nz, .moveselected + pop af + ret +.moveselected + pop af + ret nz + ld hl, wBattleMonPP + ld a, [wCurrentMenuItem] + ld c, a + ld b, $0 + add hl, bc + ld a, [hl] + and $3f + jr z, .noPP + ld a, [wPlayerDisabledMove] + swap a + and $f + dec a + cp c + jr z, .disabled + ld a, [wPlayerBattleStatus3] + bit 3, a ; transformed + jr nz, .dummy ; game freak derp +.dummy + ld a, [wCurrentMenuItem] + ld hl, wBattleMonMoves + ld c, a + ld b, $0 + add hl, bc + ld a, [hl] + ld [wPlayerSelectedMove], a + xor a + ret +.disabled + ld hl, MoveDisabledText + jr .print +.noPP + ld hl, MoveNoPPText +.print + call PrintText + call LoadScreenTilesFromBuffer1 + jp MoveSelectionMenu + +MoveNoPPText: + TX_FAR _MoveNoPPText + db "@" + +MoveDisabledText: + TX_FAR _MoveDisabledText + db "@" + +WhichTechniqueString: + db "Welche attacke?" + next " @" + +SelectMenuItem_CursorUp: + ld a, [wCurrentMenuItem] + and a + jp nz, SelectMenuItem + call EraseMenuCursor + ld a, [wNumMovesMinusOne] + inc a + ld [wCurrentMenuItem], a + jp SelectMenuItem + +SelectMenuItem_CursorDown: + ld a, [wCurrentMenuItem] + ld b, a + ld a, [wNumMovesMinusOne] + inc a + inc a + cp b + jp nz, SelectMenuItem + call EraseMenuCursor + ld a, $1 + ld [wCurrentMenuItem], a + jp SelectMenuItem + +AnyMoveToSelect: +; return z and Struggle as the selected move if all moves have 0 PP and/or are disabled + ld a, STRUGGLE + ld [wPlayerSelectedMove], a + ld a, [wPlayerDisabledMove] + and a + ld hl, wBattleMonPP + jr nz, .handleDisabledMove + ld a, [hli] + or [hl] + inc hl + or [hl] + inc hl + or [hl] + and $3f + ret nz + jr .noMovesLeft +.handleDisabledMove + swap a + and $f ; get disabled move + ld b, a + ld d, NUM_MOVES + 1 + xor a +.handleDisabledMovePPLoop + dec d + jr z, .allMovesChecked + ld c, [hl] ; get move PP + inc hl + dec b ; is this the disabled move? + jr z, .handleDisabledMovePPLoop ; if so, ignore its PP value + or c + jr .handleDisabledMovePPLoop +.allMovesChecked + and a ; any PP left? + ret nz ; return if a move has PP left +.noMovesLeft + ld hl, NoMovesLeftText + call PrintText + ld c, 60 + call DelayFrames + xor a + ret + +NoMovesLeftText: + TX_FAR _NoMovesLeftText + db "@" + +SwapMovesInMenu: + ld a, [wMenuItemToSwap] + and a + jr z, .noMenuItemSelected + ld hl, wBattleMonMoves + call .swapBytes ; swap moves + ld hl, wBattleMonPP + call .swapBytes ; swap move PP +; update the index of the disabled move if necessary + ld hl, wPlayerDisabledMove + ld a, [hl] + swap a + and $f + ld b, a + ld a, [wCurrentMenuItem] + cp b + jr nz, .next + ld a, [hl] + and $f + ld b, a + ld a, [wMenuItemToSwap] + swap a + add b + ld [hl], a + jr .swapMovesInPartyMon +.next + ld a, [wMenuItemToSwap] + cp b + jr nz, .swapMovesInPartyMon + ld a, [hl] + and $f + ld b, a + ld a, [wCurrentMenuItem] + swap a + add b + ld [hl], a +.swapMovesInPartyMon + ld hl, wPartyMon1Moves + ld a, [wPlayerMonNumber] + ld bc, wPartyMon2 - wPartyMon1 + call AddNTimes + push hl + call .swapBytes ; swap moves + pop hl + ld bc, wPartyMon1PP - wPartyMon1Moves + add hl, bc + call .swapBytes ; swap move PP + xor a + ld [wMenuItemToSwap], a ; deselect the item + jp MoveSelectionMenu +.swapBytes + push hl + ld a, [wMenuItemToSwap] + dec a + ld c, a + ld b, 0 + add hl, bc + ld d, h + ld e, l + pop hl + ld a, [wCurrentMenuItem] + dec a + ld c, a + ld b, 0 + add hl, bc + ld a, [de] + ld b, [hl] + ld [hl], a + ld a, b + ld [de], a + ret +.noMenuItemSelected + ld a, [wCurrentMenuItem] + ld [wMenuItemToSwap], a ; select the current menu item for swapping + jp MoveSelectionMenu + +PrintMenuItem: + xor a + ld [H_AUTOBGTRANSFERENABLED], a + coord hl, 0, 8 + ld b, 3 + ld c, 9 + call TextBoxBorder + ld a, [wPlayerDisabledMove] + and a + jr z, .notDisabled + swap a + and $f + ld b, a + ld a, [wCurrentMenuItem] + cp b + jr nz, .notDisabled + coord hl, 1, 10 + ld de, DisabledText + call PlaceString + jr .moveDisabled +.notDisabled + ld hl, wCurrentMenuItem + dec [hl] + xor a + ld [H_WHOSETURN], a + ld hl, wBattleMonMoves + ld a, [wCurrentMenuItem] + ld c, a + ld b, $0 ; which item in the menu is the cursor pointing to? (0-3) + add hl, bc ; point to the item (move) in memory + ld a, [hl] + ld [wPlayerSelectedMove], a ; update wPlayerSelectedMove even if the move + ; isn't actually selected (just pointed to by the cursor) + ld a, [wPlayerMonNumber] + ld [wWhichPokemon], a + ld a, BATTLE_MON_DATA + ld [wMonDataLocation], a + callab GetMaxPP + ld hl, wCurrentMenuItem + ld c, [hl] + inc [hl] + ld b, $0 + ld hl, wBattleMonPP + add hl, bc + ld a, [hl] + and $3f + ld [wcd6d], a +; print TYPE/<type> and <curPP>/<maxPP> + coord hl, 1, 9 + ld de, TypeText + call PlaceString + coord hl, 7, 11 + ld [hl], "/" + coord hl, 4, 9 + ld [hl], "/" + coord hl, 5, 11 + ld de, wcd6d + lb bc, 1, 2 + call PrintNumber + coord hl, 8, 11 + ld de, wMaxPP + lb bc, 1, 2 + call PrintNumber + call GetCurrentMove + coord hl, 2, 10 + predef PrintMoveType +.moveDisabled + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + jp Delay3 + +DisabledText: + db "BLOCKIERT@" + +TypeText: + db "TYP@" + +SelectEnemyMove: + ld a, [wLinkState] + sub LINK_STATE_BATTLING + jr nz, .noLinkBattle +; link battle + call SaveScreenTilesToBuffer1 + call LinkBattleExchangeData + call LoadScreenTilesFromBuffer1 + ld a, [wSerialExchangeNybbleReceiveData] + cp LINKBATTLE_STRUGGLE + jp z, .linkedOpponentUsedStruggle + cp LINKBATTLE_NO_ACTION + jr z, .unableToSelectMove + cp 4 + ret nc + ld [wEnemyMoveListIndex], a + ld c, a + ld hl, wEnemyMonMoves + ld b, 0 + add hl, bc + ld a, [hl] + jr .done +.noLinkBattle + ld a, [wEnemyBattleStatus2] + and (1 << NeedsToRecharge) | (1 << UsingRage) ; need to recharge or using rage + ret nz + ld hl, wEnemyBattleStatus1 + ld a, [hl] + and (1 << ChargingUp) | (1 << ThrashingAbout) ; using a charging move or thrash/petal dance + ret nz + ld a, [wEnemyMonStatus] + and SLP | 1 << FRZ ; sleeping or frozen + ret nz + ld a, [wEnemyBattleStatus1] + and (1 << UsingTrappingMove) | (1 << StoringEnergy) ; using a trapping move like wrap or bide + ret nz + ld a, [wPlayerBattleStatus1] + bit UsingTrappingMove, a ; caught in player's trapping move (e.g. wrap) + jr z, .canSelectMove +.unableToSelectMove + ld a, $ff + jr .done +.canSelectMove + ld hl, wEnemyMonMoves+1 ; 2nd enemy move + ld a, [hld] + and a + jr nz, .atLeastTwoMovesAvailable + ld a, [wEnemyDisabledMove] + and a + ld a, STRUGGLE ; struggle if the only move is disabled + jr nz, .done +.atLeastTwoMovesAvailable + ld a, [wIsInBattle] + dec a + jr z, .chooseRandomMove ; wild encounter + callab AIEnemyTrainerChooseMoves +.chooseRandomMove + push hl + call BattleRandom + ld b, $1 + cp $3f ; select move 1, [0,3e] (63/256 chance) + jr c, .moveChosen + inc hl + inc b + cp $7f ; select move 2, [3f,7e] (64/256 chance) + jr c, .moveChosen + inc hl + inc b + cp $be ; select move 3, [7f,bd] (63/256 chance) + jr c, .moveChosen + inc hl + inc b ; select move 4, [be,ff] (66/256 chance) +.moveChosen + ld a, b + dec a + ld [wEnemyMoveListIndex], a + ld a, [wEnemyDisabledMove] + swap a + and $f + cp b + ld a, [hl] + pop hl + jr z, .chooseRandomMove ; move disabled, try again + and a + jr z, .chooseRandomMove ; move non-existant, try again +.done + ld [wEnemySelectedMove], a + ret +.linkedOpponentUsedStruggle + ld a, STRUGGLE + jr .done + +; this appears to exchange data with the other gameboy during link battles +LinkBattleExchangeData: + ld a, $ff + ld [wSerialExchangeNybbleReceiveData], a + ld a, [wPlayerMoveListIndex] + cp LINKBATTLE_RUN ; is the player running from battle? + jr z, .doExchange + ld a, [wActionResultOrTookBattleTurn] + and a ; is the player switching in another mon? + jr nz, .switching +; the player used a move + ld a, [wPlayerSelectedMove] + cp STRUGGLE + ld b, LINKBATTLE_STRUGGLE + jr z, .next + dec b ; LINKBATTLE_NO_ACTION + inc a ; does move equal -1 (i.e. no action)? + jr z, .next + ld a, [wPlayerMoveListIndex] + jr .doExchange +.switching + ld a, [wWhichPokemon] + add 4 + ld b, a +.next + ld a, b +.doExchange + ld [wSerialExchangeNybbleSendData], a + callab PrintWaitingText +.syncLoop1 + call Serial_ExchangeNybble + call DelayFrame + ld a, [wSerialExchangeNybbleReceiveData] + inc a + jr z, .syncLoop1 + ld b, 10 +.syncLoop2 + call DelayFrame + call Serial_ExchangeNybble + dec b + jr nz, .syncLoop2 + ld b, 10 +.syncLoop3 + call DelayFrame + call Serial_SendZeroByte + dec b + jr nz, .syncLoop3 + ret + +ExecutePlayerMove: + xor a + ld [H_WHOSETURN], a ; set player's turn + ld a, [wPlayerSelectedMove] + inc a + jp z, ExecutePlayerMoveDone ; for selected move = FF, skip most of player's turn + xor a + ld [wMoveMissed], a + ld [wMonIsDisobedient], a + ld [wMoveDidntMiss], a + ld a, $a + ld [wDamageMultipliers], a + ld a, [wActionResultOrTookBattleTurn] + and a ; has the player already used the turn (e.g. by using an item, trying to run or switching pokemon) + jp nz, ExecutePlayerMoveDone + call PrintGhostText + jp z, ExecutePlayerMoveDone + call CheckPlayerStatusConditions + jr nz, .playerHasNoSpecialCondition + jp hl +.playerHasNoSpecialCondition + call GetCurrentMove + ld hl, wPlayerBattleStatus1 + bit ChargingUp, [hl] ; charging up for attack + jr nz, PlayerCanExecuteChargingMove + call CheckForDisobedience + jp z, ExecutePlayerMoveDone + +CheckIfPlayerNeedsToChargeUp: + ld a, [wPlayerMoveEffect] + cp CHARGE_EFFECT + jp z, JumpMoveEffect + cp FLY_EFFECT + jp z, JumpMoveEffect + jr PlayerCanExecuteMove + +; in-battle stuff +PlayerCanExecuteChargingMove: + ld hl,wPlayerBattleStatus1 + res ChargingUp,[hl] ; reset charging up and invulnerability statuses if mon was charging up for an attack + ; being fully paralyzed or hurting oneself in confusion removes charging up status + ; resulting in the Pokemon being invulnerable for the whole battle + res Invulnerable,[hl] +PlayerCanExecuteMove: + call PrintMonName1Text + ld hl,DecrementPP + ld de,wPlayerSelectedMove ; pointer to the move just used + ld b,BANK(DecrementPP) + call Bankswitch + ld a,[wPlayerMoveEffect] ; effect of the move just used + ld hl,ResidualEffects1 + ld de,1 + call IsInArray + jp c,JumpMoveEffect ; ResidualEffects1 moves skip damage calculation and accuracy tests + ; unless executed as part of their exclusive effect functions + ld a,[wPlayerMoveEffect] + ld hl,SpecialEffectsCont + ld de,1 + call IsInArray + call c,JumpMoveEffect ; execute the effects of SpecialEffectsCont moves (e.g. Wrap, Thrash) but don't skip anything +PlayerCalcMoveDamage: + ld a,[wPlayerMoveEffect] + ld hl,SetDamageEffects + ld de,1 + call IsInArray + jp c,.moveHitTest ; SetDamageEffects moves (e.g. Seismic Toss and Super Fang) skip damage calculation + call CriticalHitTest + call HandleCounterMove + jr z,handleIfPlayerMoveMissed + call GetDamageVarsForPlayerAttack + call CalculateDamage + jp z,playerCheckIfFlyOrChargeEffect ; for moves with 0 BP, skip any further damage calculation and, for now, skip MoveHitTest + ; for these moves, accuracy tests will only occur if they are called as part of the effect itself + call AdjustDamageForMoveType + call RandomizeDamage +.moveHitTest + call MoveHitTest +handleIfPlayerMoveMissed: + ld a,[wMoveMissed] + and a + jr z,getPlayerAnimationType + ld a,[wPlayerMoveEffect] + sub a,EXPLODE_EFFECT + jr z,playPlayerMoveAnimation ; don't play any animation if the move missed, unless it was EXPLODE_EFFECT + jr playerCheckIfFlyOrChargeEffect +getPlayerAnimationType: + ld a,[wPlayerMoveEffect] + and a + ld a,4 ; move has no effect other than dealing damage + jr z,playPlayerMoveAnimation + ld a,5 ; move has effect +playPlayerMoveAnimation: + push af + ld a,[wPlayerBattleStatus2] + bit HasSubstituteUp,a + ld hl,HideSubstituteShowMonAnim + ld b,BANK(HideSubstituteShowMonAnim) + call nz,Bankswitch + pop af + ld [wAnimationType],a + ld a,[wPlayerMoveNum] + call PlayMoveAnimation + call HandleExplodingAnimation + call DrawPlayerHUDAndHPBar + ld a,[wPlayerBattleStatus2] + bit HasSubstituteUp,a + ld hl,ReshowSubstituteAnim + ld b,BANK(ReshowSubstituteAnim) + call nz,Bankswitch + jr MirrorMoveCheck +playerCheckIfFlyOrChargeEffect: + ld c,30 + call DelayFrames + ld a,[wPlayerMoveEffect] + cp a,FLY_EFFECT + jr z,.playAnim + cp a,CHARGE_EFFECT + jr z,.playAnim + jr MirrorMoveCheck +.playAnim + xor a + ld [wAnimationType],a + ld a,STATUS_AFFECTED_ANIM + call PlayMoveAnimation +MirrorMoveCheck: + ld a,[wPlayerMoveEffect] + cp a,MIRROR_MOVE_EFFECT + jr nz,.metronomeCheck + call MirrorMoveCopyMove + jp z,ExecutePlayerMoveDone + xor a + ld [wMonIsDisobedient],a + jp CheckIfPlayerNeedsToChargeUp ; if Mirror Move was successful go back to damage calculation for copied move +.metronomeCheck + cp a,METRONOME_EFFECT + jr nz,.next + call MetronomePickMove + jp CheckIfPlayerNeedsToChargeUp ; Go back to damage calculation for the move picked by Metronome +.next + ld a,[wPlayerMoveEffect] + ld hl,ResidualEffects2 + ld de,1 + call IsInArray + jp c,JumpMoveEffect ; done here after executing effects of ResidualEffects2 + ld a,[wMoveMissed] + and a + jr z,.moveDidNotMiss + call PrintMoveFailureText + ld a,[wPlayerMoveEffect] + cp a,EXPLODE_EFFECT ; even if Explosion or Selfdestruct missed, its effect still needs to be activated + jr z,.notDone + jp ExecutePlayerMoveDone ; otherwise, we're done if the move missed +.moveDidNotMiss + call ApplyAttackToEnemyPokemon + call PrintCriticalOHKOText + callab DisplayEffectiveness + ld a,1 + ld [wMoveDidntMiss],a +.notDone + ld a,[wPlayerMoveEffect] + ld hl,AlwaysHappenSideEffects + ld de,1 + call IsInArray + call c,JumpMoveEffect ; not done after executing effects of AlwaysHappenSideEffects + ld hl,wEnemyMonHP + ld a,[hli] + ld b,[hl] + or b + ret z ; don't do anything else if the enemy fainted + call HandleBuildingRage + + ld hl,wPlayerBattleStatus1 + bit AttackingMultipleTimes,[hl] + jr z,.executeOtherEffects + ld a,[wPlayerNumAttacksLeft] + dec a + ld [wPlayerNumAttacksLeft],a + jp nz,getPlayerAnimationType ; for multi-hit moves, apply attack until PlayerNumAttacksLeft hits 0 or the enemy faints. + ; damage calculation and accuracy tests only happen for the first hit + res AttackingMultipleTimes,[hl] ; clear attacking multiple times status when all attacks are over + ld hl,MultiHitText + call PrintText + xor a + ld [wPlayerNumHits],a +.executeOtherEffects + ld a,[wPlayerMoveEffect] + and a + jp z,ExecutePlayerMoveDone + ld hl,SpecialEffects + ld de,1 + call IsInArray + call nc,JumpMoveEffect ; move effects not included in SpecialEffects or in either of the ResidualEffect arrays, + ; which are the effects not covered yet. Rage effect will be executed for a second time (though it's irrelevant). + ; Includes side effects that only need to be called if the target didn't faint. + ; Responsible for executing Twineedle's second side effect (poison). + jp ExecutePlayerMoveDone + +MultiHitText: + TX_FAR _MultiHitText + db "@" + +ExecutePlayerMoveDone: + xor a + ld [wActionResultOrTookBattleTurn],a + ld b,1 + ret + +PrintGhostText: +; print the ghost battle messages + call IsGhostBattle + ret nz + ld a,[H_WHOSETURN] + and a + jr nz,.Ghost + ld a,[wBattleMonStatus] ; player’s turn + and a,SLP | (1 << FRZ) + ret nz + ld hl,ScaredText + call PrintText + xor a + ret +.Ghost ; ghost’s turn + ld hl,GetOutText + call PrintText + xor a + ret + +ScaredText: + TX_FAR _ScaredText + db "@" + +GetOutText: + TX_FAR _GetOutText + db "@" + +IsGhostBattle: + ld a,[wIsInBattle] + dec a + ret nz + ld a,[wCurMap] + cp a,POKEMONTOWER_1 + jr c,.next + cp a,LAVENDER_HOUSE_1 + jr nc,.next + ld b,SILPH_SCOPE + call IsItemInBag + ret z +.next + ld a,1 + and a + ret + +; checks for various status conditions affecting the player mon +; stores whether the mon cannot use a move this turn in Z flag +CheckPlayerStatusConditions: + ld hl,wBattleMonStatus + ld a,[hl] + and a,SLP ; sleep mask + jr z,.FrozenCheck +; sleeping + dec a + ld [wBattleMonStatus],a ; decrement number of turns left + and a + jr z,.WakeUp ; if the number of turns hit 0, wake up +; fast asleep + xor a + ld [wAnimationType],a + ld a,SLP_ANIM - 1 + call PlayMoveAnimation + ld hl,FastAsleepText + call PrintText + jr .sleepDone +.WakeUp + ld hl,WokeUpText + call PrintText +.sleepDone + xor a + ld [wPlayerUsedMove],a + ld hl,ExecutePlayerMoveDone ; player can't move this turn + jp .returnToHL + +.FrozenCheck + bit FRZ,[hl] ; frozen? + jr z,.HeldInPlaceCheck + ld hl,IsFrozenText + call PrintText + xor a + ld [wPlayerUsedMove],a + ld hl,ExecutePlayerMoveDone ; player can't move this turn + jp .returnToHL + +.HeldInPlaceCheck + ld a,[wEnemyBattleStatus1] + bit UsingTrappingMove,a ; is enemy using a mult-turn move like wrap? + jp z,.FlinchedCheck + ld hl,CantMoveText + call PrintText + ld hl,ExecutePlayerMoveDone ; player can't move this turn + jp .returnToHL + +.FlinchedCheck + ld hl,wPlayerBattleStatus1 + bit Flinched,[hl] + jp z,.HyperBeamCheck + res Flinched,[hl] ; reset player's flinch status + ld hl,FlinchedText + call PrintText + ld hl,ExecutePlayerMoveDone ; player can't move this turn + jp .returnToHL + +.HyperBeamCheck + ld hl,wPlayerBattleStatus2 + bit NeedsToRecharge,[hl] + jr z,.AnyMoveDisabledCheck + res NeedsToRecharge,[hl] ; reset player's recharge status + ld hl,MustRechargeText + call PrintText + ld hl,ExecutePlayerMoveDone ; player can't move this turn + jp .returnToHL + +.AnyMoveDisabledCheck + ld hl,wPlayerDisabledMove + ld a,[hl] + and a + jr z,.ConfusedCheck + dec a + ld [hl],a + and $f ; did Disable counter hit 0? + jr nz,.ConfusedCheck + ld [hl],a + ld [wPlayerDisabledMoveNumber],a + ld hl,DisabledNoMoreText + call PrintText + +.ConfusedCheck + ld a,[wPlayerBattleStatus1] + add a ; is player confused? + jr nc,.TriedToUseDisabledMoveCheck + ld hl,wPlayerConfusedCounter + dec [hl] + jr nz,.IsConfused + ld hl,wPlayerBattleStatus1 + res Confused,[hl] ; if confused counter hit 0, reset confusion status + ld hl,ConfusedNoMoreText + call PrintText + jr .TriedToUseDisabledMoveCheck +.IsConfused + ld hl,IsConfusedText + call PrintText + xor a + ld [wAnimationType],a + ld a,CONF_ANIM - 1 + call PlayMoveAnimation + call BattleRandom + cp a,$80 ; 50% chance to hurt itself + jr c,.TriedToUseDisabledMoveCheck + ld hl,wPlayerBattleStatus1 + ld a,[hl] + and a, 1 << Confused ; if mon hurts itself, clear every other status from wPlayerBattleStatus1 + ld [hl],a + call HandleSelfConfusionDamage + jr .MonHurtItselfOrFullyParalysed + +.TriedToUseDisabledMoveCheck +; prevents a disabled move that was selected before being disabled from being used + ld a,[wPlayerDisabledMoveNumber] + and a + jr z,.ParalysisCheck + ld hl,wPlayerSelectedMove + cp [hl] + jr nz,.ParalysisCheck + call PrintMoveIsDisabledText + ld hl,ExecutePlayerMoveDone ; if a disabled move was somehow selected, player can't move this turn + jp .returnToHL + +.ParalysisCheck + ld hl,wBattleMonStatus + bit PAR,[hl] + jr z,.BideCheck + call BattleRandom + cp a,$3F ; 25% to be fully paralyzed + jr nc,.BideCheck + ld hl,FullyParalyzedText + call PrintText + +.MonHurtItselfOrFullyParalysed + ld hl,wPlayerBattleStatus1 + ld a,[hl] + ; clear bide, thrashing, charging up, and trapping moves such as warp (already cleared for confusion damage) + and $ff ^ ((1 << StoringEnergy) | (1 << ThrashingAbout) | (1 << ChargingUp) | (1 << UsingTrappingMove)) + ld [hl],a + ld a,[wPlayerMoveEffect] + cp a,FLY_EFFECT + jr z,.FlyOrChargeEffect + cp a,CHARGE_EFFECT + jr z,.FlyOrChargeEffect + jr .NotFlyOrChargeEffect + +.FlyOrChargeEffect + xor a + ld [wAnimationType],a + ld a,STATUS_AFFECTED_ANIM + call PlayMoveAnimation +.NotFlyOrChargeEffect + ld hl,ExecutePlayerMoveDone + jp .returnToHL ; if using a two-turn move, we need to recharge the first turn + +.BideCheck + ld hl,wPlayerBattleStatus1 + bit StoringEnergy,[hl] ; is mon using bide? + jr z,.ThrashingAboutCheck + xor a + ld [wPlayerMoveNum],a + ld hl,wDamage + ld a,[hli] + ld b,a + ld c,[hl] + ld hl,wPlayerBideAccumulatedDamage + 1 + ld a,[hl] + add c ; accumulate damage taken + ld [hld],a + ld a,[hl] + adc b + ld [hl],a + ld hl,wPlayerNumAttacksLeft + dec [hl] ; did Bide counter hit 0? + jr z,.UnleashEnergy + ld hl,ExecutePlayerMoveDone + jp .returnToHL ; unless mon unleashes energy, can't move this turn +.UnleashEnergy + ld hl,wPlayerBattleStatus1 + res StoringEnergy,[hl] ; not using bide any more + ld hl,UnleashedEnergyText + call PrintText + ld a,1 + ld [wPlayerMovePower],a + ld hl,wPlayerBideAccumulatedDamage + 1 + ld a,[hld] + add a + ld b,a + ld [wDamage + 1],a + ld a,[hl] + rl a ; double the damage + ld [wDamage],a + or b + jr nz,.next + ld a,1 + ld [wMoveMissed],a +.next + xor a + ld [hli],a + ld [hl],a + ld a,BIDE + ld [wPlayerMoveNum],a + ld hl,handleIfPlayerMoveMissed ; skip damage calculation, DecrementPP and MoveHitTest + jp .returnToHL + +.ThrashingAboutCheck + bit ThrashingAbout,[hl] ; is mon using thrash or petal dance? + jr z,.MultiturnMoveCheck + ld a,THRASH + ld [wPlayerMoveNum],a + ld hl,ThrashingAboutText + call PrintText + ld hl,wPlayerNumAttacksLeft + dec [hl] ; did Thrashing About counter hit 0? + ld hl,PlayerCalcMoveDamage ; skip DecrementPP + jp nz,.returnToHL + push hl + ld hl,wPlayerBattleStatus1 + res ThrashingAbout,[hl] ; no longer thrashing about + set Confused,[hl] ; confused + call BattleRandom + and a,3 + inc a + inc a ; confused for 2-5 turns + ld [wPlayerConfusedCounter],a + pop hl ; skip DecrementPP + jp .returnToHL + +.MultiturnMoveCheck + bit UsingTrappingMove,[hl] ; is mon using multi-turn move? + jp z,.RageCheck + ld hl,AttackContinuesText + call PrintText + ld a,[wPlayerNumAttacksLeft] + dec a ; did multi-turn move end? + ld [wPlayerNumAttacksLeft],a + ld hl,getPlayerAnimationType ; if it didn't, skip damage calculation (deal damage equal to last hit), + ; DecrementPP and MoveHitTest + jp nz,.returnToHL + jp .returnToHL + +.RageCheck + ld a, [wPlayerBattleStatus2] + bit UsingRage, a ; is mon using rage? + jp z, .checkPlayerStatusConditionsDone ; if we made it this far, mon can move normally this turn + ld a, RAGE + ld [wd11e], a + call GetMoveName + call CopyStringToCF50 + xor a + ld [wPlayerMoveEffect], a + ld hl, PlayerCanExecuteMove + jp .returnToHL + +.returnToHL + xor a + ret + +.checkPlayerStatusConditionsDone + ld a, $1 + and a + ret + +FastAsleepText: + TX_FAR _FastAsleepText + db "@" + +WokeUpText: + TX_FAR _WokeUpText + db "@" + +IsFrozenText: + TX_FAR _IsFrozenText + db "@" + +FullyParalyzedText: + TX_FAR _FullyParalyzedText + db "@" + +FlinchedText: + TX_FAR _FlinchedText + db "@" + +MustRechargeText: + TX_FAR _MustRechargeText + db "@" + +DisabledNoMoreText: + TX_FAR _DisabledNoMoreText + db "@" + +IsConfusedText: + TX_FAR _IsConfusedText + db "@" + +HurtItselfText: + TX_FAR _HurtItselfText + db "@" + +ConfusedNoMoreText: + TX_FAR _ConfusedNoMoreText + db "@" + +SavingEnergyText: + TX_FAR _SavingEnergyText + db "@" + +UnleashedEnergyText: + TX_FAR _UnleashedEnergyText + db "@" + +ThrashingAboutText: + TX_FAR _ThrashingAboutText + db "@" + +AttackContinuesText: + TX_FAR _AttackContinuesText + db "@" + +CantMoveText: + TX_FAR _CantMoveText + db "@" + +PrintMoveIsDisabledText: + ld hl, wPlayerSelectedMove + ld de, wPlayerBattleStatus1 + ld a, [H_WHOSETURN] + and a + jr z, .removeChargingUp + inc hl + ld de, wEnemyBattleStatus1 +.removeChargingUp + ld a, [de] + res ChargingUp, a ; end the pokemon's + ld [de], a + ld a, [hl] + ld [wd11e], a + call GetMoveName + ld hl, MoveIsDisabledText + jp PrintText + +MoveIsDisabledText: + TX_FAR _MoveIsDisabledText + db "@" + +HandleSelfConfusionDamage: + ld hl, HurtItselfText + call PrintText + ld hl, wEnemyMonDefense + ld a, [hli] + push af + ld a, [hld] + push af + ld a, [wBattleMonDefense] + ld [hli], a + ld a, [wBattleMonDefense + 1] + ld [hl], a + ld hl, wPlayerMoveEffect + push hl + ld a, [hl] + push af + xor a + ld [hli], a + ld [wCriticalHitOrOHKO], a ; self-inflicted confusion damage can't be a Critical Hit + ld a, 40 ; 40 base power + ld [hli], a + xor a + ld [hl], a + call GetDamageVarsForPlayerAttack + call CalculateDamage ; ignores AdjustDamageForMoveType (type-less damage), RandomizeDamage, + ; and MoveHitTest (always hits) + pop af + pop hl + ld [hl], a + ld hl, wEnemyMonDefense + 1 + pop af + ld [hld], a + pop af + ld [hl], a + xor a + ld [wAnimationType], a + inc a + ld [H_WHOSETURN], a + call PlayMoveAnimation + call DrawPlayerHUDAndHPBar + xor a + ld [H_WHOSETURN], a + jp ApplyDamageToPlayerPokemon + +PrintMonName1Text: + ld hl, MonName1Text + jp PrintText + +; this function wastes time calling DetermineExclamationPointTextNum +; and choosing between Used1Text and Used2Text, even though +; those text strings are identical and both continue at PrintInsteadText +; this likely had to do with Japanese grammar that got translated, +; but the functionality didn't get removed +MonName1Text: + TX_FAR _MonName1Text + TX_ASM + ld a, [H_WHOSETURN] + and a + ld a, [wPlayerMoveNum] + ld hl, wPlayerUsedMove + jr z, .playerTurn + ld a, [wEnemyMoveNum] + ld hl, wEnemyUsedMove +.playerTurn + ld [hl], a + ld [wd11e], a + call DetermineExclamationPointTextNum + ld a, [wMonIsDisobedient] + and a + ld hl, Used2Text + ret nz + ld a, [wd11e] + cp 3 + ld hl, Used2Text + ret c + ld hl, Used1Text + ret + +Used1Text: + TX_FAR _Used1Text + TX_ASM + jr PrintInsteadText + +Used2Text: + TX_FAR _Used2Text + TX_ASM + ; fall through + +PrintInsteadText: + ld a, [wMonIsDisobedient] + and a + jr z, PrintMoveName + ld hl, InsteadText + ret + +InsteadText: + TX_FAR _InsteadText + TX_ASM + ; fall through + +PrintMoveName: + ld hl, _PrintMoveName + ret + +_PrintMoveName: + TX_FAR _CF50Text + TX_ASM + ld hl, ExclamationPointPointerTable + ld a, [wd11e] ; exclamation point num + add a + push bc + ld b, $0 + ld c, a + add hl, bc + pop bc + ld a, [hli] + ld h, [hl] + ld l, a + ret + +ExclamationPointPointerTable: + dw ExclamationPoint1Text + dw ExclamationPoint2Text + dw ExclamationPoint3Text + dw ExclamationPoint4Text + dw ExclamationPoint5Text + +ExclamationPoint1Text: + TX_FAR _ExclamationPoint1Text + db "@" + +ExclamationPoint2Text: + TX_FAR _ExclamationPoint2Text + db "@" + +ExclamationPoint3Text: + TX_FAR _ExclamationPoint3Text + db "@" + +ExclamationPoint4Text: + TX_FAR _ExclamationPoint4Text + db "@" + +ExclamationPoint5Text: + TX_FAR _ExclamationPoint5Text + db "@" + +; this function does nothing useful +; if the move being used is in set [1-4] from ExclamationPointMoveSets, +; use ExclamationPoint[1-4]Text +; otherwise, use ExclamationPoint5Text +; but all five text strings are identical +; this likely had to do with Japanese grammar that got translated, +; but the functionality didn't get removed +DetermineExclamationPointTextNum: + push bc + ld a, [wd11e] ; move ID + ld c, a + ld b, $0 + ld hl, ExclamationPointMoveSets +.loop + ld a, [hli] + cp $ff + jr z, .done + cp c + jr z, .done + and a + jr nz, .loop + inc b + jr .loop +.done + ld a, b + ld [wd11e], a ; exclamation point num + pop bc + ret + +ExclamationPointMoveSets: + db SWORDS_DANCE, GROWTH + db $00 + db RECOVER, BIDE, SELFDESTRUCT, AMNESIA + db $00 + db MEDITATE, AGILITY, TELEPORT, MIMIC, DOUBLE_TEAM, BARRAGE + db $00 + db POUND, SCRATCH, VICEGRIP, WING_ATTACK, FLY, BIND, SLAM, HORN_ATTACK, BODY_SLAM + db WRAP, THRASH, TAIL_WHIP, LEER, BITE, GROWL, ROAR, SING, PECK, COUNTER + db STRENGTH, ABSORB, STRING_SHOT, EARTHQUAKE, FISSURE, DIG, TOXIC, SCREECH, HARDEN + db MINIMIZE, WITHDRAW, DEFENSE_CURL, METRONOME, LICK, CLAMP, CONSTRICT, POISON_GAS + db LEECH_LIFE, BUBBLE, FLASH, SPLASH, ACID_ARMOR, FURY_SWIPES, REST, SHARPEN, SLASH, SUBSTITUTE + db $00 + db $FF ; terminator + +PrintMoveFailureText: + ld de, wPlayerMoveEffect + ld a, [H_WHOSETURN] + and a + jr z, .playersTurn + ld de, wEnemyMoveEffect +.playersTurn + ld hl, DoesntAffectMonText + ld a, [wDamageMultipliers] + and $7f + jr z, .gotTextToPrint + ld hl, AttackMissedText + ld a, [wCriticalHitOrOHKO] + cp $ff + jr nz, .gotTextToPrint + ld hl, UnaffectedText +.gotTextToPrint + push de + call PrintText + xor a + ld [wCriticalHitOrOHKO], a + pop de + ld a, [de] + cp JUMP_KICK_EFFECT + ret nz + + ; if you get here, the mon used jump kick or hi jump kick and missed + ld hl, wDamage ; since the move missed, wDamage will always contain 0 at this point. + ; Thus, recoil damage will always be equal to 1 + ; even if it was intended to be potential damage/8. + ld a, [hli] + ld b, [hl] + srl a + rr b + srl a + rr b + srl a + rr b + ld [hl], b + dec hl + ld [hli], a + or b + jr nz, .applyRecoil + inc a + ld [hl], a +.applyRecoil + ld hl, KeptGoingAndCrashedText + call PrintText + ld b, $4 + predef PredefShakeScreenHorizontally + ld a, [H_WHOSETURN] + and a + jr nz, .enemyTurn + jp ApplyDamageToPlayerPokemon +.enemyTurn + jp ApplyDamageToEnemyPokemon + +AttackMissedText: + TX_FAR _AttackMissedText + db "@" + +KeptGoingAndCrashedText: + TX_FAR _KeptGoingAndCrashedText + db "@" + +UnaffectedText: + TX_FAR _UnaffectedText + db "@" + +PrintDoesntAffectText: + ld hl, DoesntAffectMonText + jp PrintText + +DoesntAffectMonText: + TX_FAR _DoesntAffectMonText + db "@" + +; if there was a critical hit or an OHKO was successful, print the corresponding text +PrintCriticalOHKOText: + ld a, [wCriticalHitOrOHKO] + and a + jr z, .done ; do nothing if there was no critical hit or successful OHKO + dec a + add a + ld hl, CriticalOHKOTextPointers + ld b, $0 + ld c, a + add hl, bc + ld a, [hli] + ld h, [hl] + ld l, a + call PrintText + xor a + ld [wCriticalHitOrOHKO], a +.done + ld c, 20 + jp DelayFrames + +CriticalOHKOTextPointers: + dw CriticalHitText + dw OHKOText + +CriticalHitText: + TX_FAR _CriticalHitText + db "@" + +OHKOText: + TX_FAR _OHKOText + db "@" + +; checks if a traded mon will disobey due to lack of badges +; stores whether the mon will use a move in Z flag +CheckForDisobedience: + xor a + ld [wMonIsDisobedient], a + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr nz, .checkIfMonIsTraded + ld a, $1 + and a + ret +; compare the mon's original trainer ID with the player's ID to see if it was traded +.checkIfMonIsTraded + ld hl, wPartyMon1OTID + ld bc, wPartyMon2 - wPartyMon1 + ld a, [wPlayerMonNumber] + call AddNTimes + ld a, [wPlayerID] + cp [hl] + jr nz, .monIsTraded + inc hl + ld a, [wPlayerID + 1] + cp [hl] + jp z, .canUseMove +; it was traded +.monIsTraded +; what level might disobey? + ld hl, wObtainedBadges + bit 7, [hl] + ld a, 101 + jr nz, .next + bit 5, [hl] + ld a, 70 + jr nz, .next + bit 3, [hl] + ld a, 50 + jr nz, .next + bit 1, [hl] + ld a, 30 + jr nz, .next + ld a, 10 +.next + ld b, a + ld c, a + ld a, [wBattleMonLevel] + ld d, a + add b + ld b, a + jr nc, .noCarry + ld b, $ff ; cap b at $ff +.noCarry + ld a, c + cp d + jp nc, .canUseMove +.loop1 + call BattleRandom + swap a + cp b + jr nc, .loop1 + cp c + jp c, .canUseMove +.loop2 + call BattleRandom + cp b + jr nc, .loop2 + cp c + jr c, .useRandomMove + ld a, d + sub c + ld b, a + call BattleRandom + swap a + sub b + jr c, .monNaps + cp b + jr nc, .monDoesNothing + ld hl, WontObeyText + call PrintText + call HandleSelfConfusionDamage + jp .cannotUseMove +.monNaps + call BattleRandom + add a + swap a + and SLP ; sleep mask + jr z, .monNaps ; keep trying until we get at least 1 turn of sleep + ld [wBattleMonStatus], a + ld hl, BeganToNapText + jr .printText +.monDoesNothing + call BattleRandom + and $3 + ld hl, LoafingAroundText + and a + jr z, .printText + ld hl, WontObeyText + dec a + jr z, .printText + ld hl, TurnedAwayText + dec a + jr z, .printText + ld hl, IgnoredOrdersText +.printText + call PrintText + jr .cannotUseMove +.useRandomMove + ld a, [wBattleMonMoves + 1] + and a ; is the second move slot empty? + jr z, .monDoesNothing ; mon will not use move if it only knows one move + ld a, [wPlayerDisabledMoveNumber] + and a + jr nz, .monDoesNothing + ld a, [wPlayerSelectedMove] + cp STRUGGLE + jr z, .monDoesNothing ; mon will not use move if struggling +; check if only one move has remaining PP + ld hl, wBattleMonPP + push hl + ld a, [hli] + and $3f + ld b, a + ld a, [hli] + and $3f + add b + ld b, a + ld a, [hli] + and $3f + add b + ld b, a + ld a, [hl] + and $3f + add b + pop hl + push af + ld a, [wCurrentMenuItem] + ld c, a + ld b, $0 + add hl, bc + ld a, [hl] + and $3f + ld b, a + pop af + cp b + jr z, .monDoesNothing ; mon will not use move if only one move has remaining PP + ld a, $1 + ld [wMonIsDisobedient], a + ld a, [wMaxMenuItem] + ld b, a + ld a, [wCurrentMenuItem] + ld c, a +.chooseMove + call BattleRandom + and $3 + cp b + jr nc, .chooseMove ; if the random number is greater than the move count, choose another + cp c + jr z, .chooseMove ; if the random number matches the move the player selected, choose another + ld [wCurrentMenuItem], a + ld hl, wBattleMonPP + ld e, a + ld d, $0 + add hl, de + ld a, [hl] + and a ; does the move have any PP left? + jr z, .chooseMove ; if the move has no PP left, choose another + ld a, [wCurrentMenuItem] + ld c, a + ld b, $0 + ld hl, wBattleMonMoves + add hl, bc + ld a, [hl] + ld [wPlayerSelectedMove], a + call GetCurrentMove +.canUseMove + ld a, $1 + and a; clear Z flag + ret +.cannotUseMove + xor a ; set Z flag + ret + +LoafingAroundText: + TX_FAR _LoafingAroundText + db "@" + +BeganToNapText: + TX_FAR _BeganToNapText + db "@" + +WontObeyText: + TX_FAR _WontObeyText + db "@" + +TurnedAwayText: + TX_FAR _TurnedAwayText + db "@" + +IgnoredOrdersText: + TX_FAR _IgnoredOrdersText + db "@" + +; sets b, c, d, and e for the CalculateDamage routine in the case of an attack by the player mon +GetDamageVarsForPlayerAttack: + xor a + ld hl, wDamage ; damage to eventually inflict, initialise to zero + ldi [hl], a + ld [hl], a + ld hl, wPlayerMovePower + ld a, [hli] + and a + ld d, a ; d = move power + ret z ; return if move power is zero + ld a, [hl] ; a = [wPlayerMoveType] + cp FIRE ; types >= FIRE are all special + jr nc, .specialAttack +.physicalAttack + ld hl, wEnemyMonDefense + ld a, [hli] + ld b, a + ld c, [hl] ; bc = enemy defense + ld a, [wEnemyBattleStatus3] + bit HasReflectUp, a ; check for Reflect + jr z, .physicalAttackCritCheck +; if the enemy has used Reflect, double the enemy's defense + sla c + rl b +.physicalAttackCritCheck + ld hl, wBattleMonAttack + ld a, [wCriticalHitOrOHKO] + and a ; check for critical hit + jr z, .scaleStats +; in the case of a critical hit, reset the player's attack and the enemy's defense to their base values + ld c, 3 ; defense stat + call GetEnemyMonStat + ld a, [H_PRODUCT + 2] + ld b, a + ld a, [H_PRODUCT + 3] + ld c, a + push bc + ld hl, wPartyMon1Attack + ld a, [wPlayerMonNumber] + ld bc, wPartyMon2 - wPartyMon1 + call AddNTimes + pop bc + jr .scaleStats +.specialAttack + ld hl, wEnemyMonSpecial + ld a, [hli] + ld b, a + ld c, [hl] ; bc = enemy special + ld a, [wEnemyBattleStatus3] + bit HasLightScreenUp, a ; check for Light Screen + jr z, .specialAttackCritCheck +; if the enemy has used Light Screen, double the enemy's special + sla c + rl b +; reflect and light screen boosts do not cap the stat at 999, so weird things will happen during stats scaling if +; a Pokemon with 512 or more Defense has used Reflect, or if a Pokemon with 512 or more Special has used Light Screen +.specialAttackCritCheck + ld hl, wBattleMonSpecial + ld a, [wCriticalHitOrOHKO] + and a ; check for critical hit + jr z, .scaleStats +; in the case of a critical hit, reset the player's and enemy's specials to their base values + ld c, 5 ; special stat + call GetEnemyMonStat + ld a, [H_PRODUCT + 2] + ld b, a + ld a, [H_PRODUCT + 3] + ld c, a + push bc + ld hl, wPartyMon1Special + ld a, [wPlayerMonNumber] + ld bc, wPartyMon2 - wPartyMon1 + call AddNTimes + pop bc +; if either the offensive or defensive stat is too large to store in a byte, scale both stats by dividing them by 4 +; this allows values with up to 10 bits (values up to 1023) to be handled +; anything larger will wrap around +.scaleStats + ld a, [hli] + ld l, [hl] + ld h, a ; hl = player's offensive stat + or b ; is either high byte nonzero? + jr z, .next ; if not, we don't need to scale +; bc /= 4 (scale enemy's defensive stat) + srl b + rr c + srl b + rr c +; defensive stat can actually end up as 0, leading to a division by 0 freeze during damage calculation +; hl /= 4 (scale player's offensive stat) + srl h + rr l + srl h + rr l + ld a, l + or h ; is the player's offensive stat 0? + jr nz, .next + inc l ; if the player's offensive stat is 0, bump it up to 1 +.next + ld b, l ; b = player's offensive stat (possibly scaled) + ; (c already contains enemy's defensive stat (possibly scaled)) + ld a, [wBattleMonLevel] + ld e, a ; e = level + ld a, [wCriticalHitOrOHKO] + and a ; check for critical hit + jr z, .done + sla e ; double level if it was a critical hit +.done + ld a, 1 + and a + ret + +; sets b, c, d, and e for the CalculateDamage routine in the case of an attack by the enemy mon +GetDamageVarsForEnemyAttack: + ld hl, wDamage ; damage to eventually inflict, initialise to zero + xor a + ld [hli], a + ld [hl], a + ld hl, wEnemyMovePower + ld a, [hli] + ld d, a ; d = move power + and a + ret z ; return if move power is zero + ld a, [hl] ; a = [wEnemyMoveType] + cp FIRE ; types >= FIRE are all special + jr nc, .specialAttack +.physicalAttack + ld hl, wBattleMonDefense + ld a, [hli] + ld b, a + ld c, [hl] ; bc = player defense + ld a, [wPlayerBattleStatus3] + bit HasReflectUp, a ; check for Reflect + jr z, .physicalAttackCritCheck +; if the player has used Reflect, double the player's defense + sla c + rl b +.physicalAttackCritCheck + ld hl, wEnemyMonAttack + ld a, [wCriticalHitOrOHKO] + and a ; check for critical hit + jr z, .scaleStats +; in the case of a critical hit, reset the player's defense and the enemy's attack to their base values + ld hl, wPartyMon1Defense + ld a, [wPlayerMonNumber] + ld bc, wPartyMon2 - wPartyMon1 + call AddNTimes + ld a, [hli] + ld b, a + ld c, [hl] + push bc + ld c, 2 ; attack stat + call GetEnemyMonStat + ld hl, H_PRODUCT + 2 + pop bc + jr .scaleStats +.specialAttack + ld hl, wBattleMonSpecial + ld a, [hli] + ld b, a + ld c, [hl] + ld a, [wPlayerBattleStatus3] + bit HasLightScreenUp, a ; check for Light Screen + jr z, .specialAttackCritCheck +; if the player has used Light Screen, double the player's special + sla c + rl b +; reflect and light screen boosts do not cap the stat at 999, so weird things will happen during stats scaling if +; a Pokemon with 512 or more Defense has used Reflect, or if a Pokemon with 512 or more Special has used Light Screen +.specialAttackCritCheck + ld hl, wEnemyMonSpecial + ld a, [wCriticalHitOrOHKO] + and a ; check for critical hit + jr z, .scaleStats +; in the case of a critical hit, reset the player's and enemy's specials to their base values + ld hl, wPartyMon1Special + ld a, [wPlayerMonNumber] + ld bc, wPartyMon2 - wPartyMon1 + call AddNTimes + ld a, [hli] + ld b, a + ld c, [hl] + push bc + ld c, 5 ; special stat + call GetEnemyMonStat + ld hl, H_PRODUCT + 2 + pop bc +; if either the offensive or defensive stat is too large to store in a byte, scale both stats by dividing them by 4 +; this allows values with up to 10 bits (values up to 1023) to be handled +; anything larger will wrap around +.scaleStats + ld a, [hli] + ld l, [hl] + ld h, a ; hl = enemy's offensive stat + or b ; is either high byte nonzero? + jr z, .next ; if not, we don't need to scale +; bc /= 4 (scale player's defensive stat) + srl b + rr c + srl b + rr c +; defensive stat can actually end up as 0, leading to a division by 0 freeze during damage calculation +; hl /= 4 (scale enemy's offensive stat) + srl h + rr l + srl h + rr l + ld a, l + or h ; is the enemy's offensive stat 0? + jr nz, .next + inc l ; if the enemy's offensive stat is 0, bump it up to 1 +.next + ld b, l ; b = enemy's offensive stat (possibly scaled) + ; (c already contains player's defensive stat (possibly scaled)) + ld a, [wEnemyMonLevel] + ld e, a + ld a, [wCriticalHitOrOHKO] + and a ; check for critical hit + jr z, .done + sla e ; double level if it was a critical hit +.done + ld a, $1 + and a + and a + ret + +; get stat c of enemy mon +; c: stat to get (HP=1,Attack=2,Defense=3,Speed=4,Special=5) +GetEnemyMonStat: + push de + push bc + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr nz, .notLinkBattle + ld hl, wEnemyMon1Stats + dec c + sla c + ld b, $0 + add hl, bc + ld a, [wEnemyMonPartyPos] + ld bc, wEnemyMon2 - wEnemyMon1 + call AddNTimes + ld a, [hli] + ld [H_MULTIPLICAND + 1], a + ld a, [hl] + ld [H_MULTIPLICAND + 2], a + pop bc + pop de + ret +.notLinkBattle + ld a, [wEnemyMonLevel] + ld [wCurEnemyLVL], a + ld a, [wEnemyMonSpecies] + ld [wd0b5], a + call GetMonHeader + ld hl, wEnemyMonDVs + ld de, wLoadedMonSpeedExp + ld a, [hli] + ld [de], a + inc de + ld a, [hl] + ld [de], a + pop bc + ld b, $0 + ld hl, wLoadedMonSpeedExp - $b ; this base address makes CalcStat look in [wLoadedMonSpeedExp] for DVs + call CalcStat + pop de + ret + +CalculateDamage: +; input: +; b: attack +; c: opponent defense +; d: base power +; e: level + + ld a, [H_WHOSETURN] ; whose turn? + and a + ld a, [wPlayerMoveEffect] + jr z, .effect + ld a, [wEnemyMoveEffect] +.effect + +; EXPLODE_EFFECT halves defense. + cp a, EXPLODE_EFFECT + jr nz, .ok + srl c + jr nz, .ok + inc c ; ...with a minimum value of 1 (used as a divisor later on) +.ok + +; Multi-hit attacks may or may not have 0 bp. + cp a, TWO_TO_FIVE_ATTACKS_EFFECT + jr z, .skipbp + cp a, $1e + jr z, .skipbp + +; Calculate OHKO damage based on remaining HP. + cp a, OHKO_EFFECT + jp z, JumpToOHKOMoveEffect + +; Don't calculate damage for moves that don't do any. + ld a, d ; base power + and a + ret z +.skipbp + + xor a + ld hl, H_DIVIDEND + ldi [hl], a + ldi [hl], a + ld [hl], a + +; Multiply level by 2 + ld a, e ; level + add a + jr nc, .nc + push af + ld a, 1 + ld [hl], a + pop af +.nc + inc hl + ldi [hl], a + +; Divide by 5 + ld a, 5 + ldd [hl], a + push bc + ld b, 4 + call Divide + pop bc + +; Add 2 + inc [hl] + inc [hl] + + inc hl ; multiplier + +; Multiply by attack base power + ld [hl], d + call Multiply + +; Multiply by attack stat + ld [hl], b + call Multiply + +; Divide by defender's defense stat + ld [hl], c + ld b, 4 + call Divide + +; Divide by 50 + ld [hl], 50 + ld b, 4 + call Divide + + ld hl, wDamage + ld b, [hl] + ld a, [H_QUOTIENT + 3] + add b + ld [H_QUOTIENT + 3], a + jr nc, .asm_3dfd0 + + ld a, [H_QUOTIENT + 2] + inc a + ld [H_QUOTIENT + 2], a + and a + jr z, .asm_3e004 + +.asm_3dfd0 + ld a, [H_QUOTIENT] + ld b, a + ld a, [H_QUOTIENT + 1] + or a + jr nz, .asm_3e004 + + ld a, [H_QUOTIENT + 2] + cp 998 / $100 + jr c, .asm_3dfe8 + cp 998 / $100 + 1 + jr nc, .asm_3e004 + ld a, [H_QUOTIENT + 3] + cp 998 % $100 + jr nc, .asm_3e004 + +.asm_3dfe8 + inc hl + ld a, [H_QUOTIENT + 3] + ld b, [hl] + add b + ld [hld], a + + ld a, [H_QUOTIENT + 2] + ld b, [hl] + adc b + ld [hl], a + jr c, .asm_3e004 + + ld a, [hl] + cp 998 / $100 + jr c, .asm_3e00a + cp 998 / $100 + 1 + jr nc, .asm_3e004 + inc hl + ld a, [hld] + cp 998 % $100 + jr c, .asm_3e00a + +.asm_3e004 +; cap at 997 + ld a, 997 / $100 + ld [hli], a + ld a, 997 % $100 + ld [hld], a + +.asm_3e00a +; add 2 + inc hl + ld a, [hl] + add 2 + ld [hld], a + jr nc, .done + inc [hl] + +.done +; minimum damage is 1 + ld a, 1 + and a + ret + +JumpToOHKOMoveEffect: + call JumpMoveEffect + ld a, [wMoveMissed] + dec a + ret + + +UnusedHighCriticalMoves: + db KARATE_CHOP + db RAZOR_LEAF + db CRABHAMMER + db SLASH + db $FF + +; determines if attack is a critical hit +; azure heights claims "the fastest pokémon (who are,not coincidentally, +; among the most popular) tend to CH about 20 to 25% of the time." +CriticalHitTest: + xor a + ld [wCriticalHitOrOHKO], a + ld a, [H_WHOSETURN] + and a + ld a, [wEnemyMonSpecies] + jr nz, .handleEnemy + ld a, [wBattleMonSpecies] +.handleEnemy + ld [wd0b5], a + call GetMonHeader + ld a, [wMonHBaseSpeed] + ld b, a + srl b ; (effective (base speed/2)) + ld a, [H_WHOSETURN] + and a + ld hl, wPlayerMovePower + ld de, wPlayerBattleStatus2 + jr z, .calcCriticalHitProbability + ld hl, wEnemyMovePower + ld de, wEnemyBattleStatus2 +.calcCriticalHitProbability + ld a, [hld] ; read base power from RAM + and a + ret z ; do nothing if zero + dec hl + ld c, [hl] ; read move id + ld a, [de] + bit GettingPumped, a ; test for focus energy + jr nz, .focusEnergyUsed ; bug: using focus energy causes a shift to the right instead of left, + ; resulting in 1/4 the usual crit chance + sla b ; (effective (base speed/2)*2) + jr nc, .noFocusEnergyUsed + ld b, $ff ; cap at 255/256 + jr .noFocusEnergyUsed +.focusEnergyUsed + srl b +.noFocusEnergyUsed + ld hl, HighCriticalMoves ; table of high critical hit moves +.Loop + ld a, [hli] ; read move from move table + cp c ; does it match the move about to be used? + jr z, .HighCritical ; if so, the move about to be used is a high critical hit ratio move + inc a ; move on to the next move, FF terminates loop + jr nz, .Loop ; check the next move in HighCriticalMoves + srl b ; /2 for regular move (effective (base speed / 2)) + jr .SkipHighCritical ; continue as a normal move +.HighCritical + sla b ; *2 for high critical hit moves + jr nc, .noCarry + ld b, $ff ; cap at 255/256 +.noCarry + sla b ; *4 for high critical move (effective (base speed/2)*8)) + jr nc, .SkipHighCritical + ld b, $ff +.SkipHighCritical + call BattleRandom ; generates a random value, in "a" + rlc a + rlc a + rlc a + cp b ; check a against calculated crit rate + ret nc ; no critical hit if no borrow + ld a, $1 + ld [wCriticalHitOrOHKO], a ; set critical hit flag + ret + +; high critical hit moves +HighCriticalMoves: + db KARATE_CHOP + db RAZOR_LEAF + db CRABHAMMER + db SLASH + db $FF + + +; function to determine if Counter hits and if so, how much damage it does +HandleCounterMove: +; The variables checked by Counter are updated whenever the cursor points to a new move in the battle selection menu. +; This is irrelevant for the opponent's side outside of link battles, since the move selection is controlled by the AI. +; However, in the scenario where the player switches out and the opponent uses Counter, +; the outcome may be affected by the player's actions in the move selection menu prior to switching the Pokemon. +; This might also lead to desync glitches in link battles. + + ld a,[H_WHOSETURN] ; whose turn + and a +; player's turn + ld hl,wEnemySelectedMove + ld de,wEnemyMovePower + ld a,[wPlayerSelectedMove] + jr z,.next +; enemy's turn + ld hl,wPlayerSelectedMove + ld de,wPlayerMovePower + ld a,[wEnemySelectedMove] +.next + cp a,COUNTER + ret nz ; return if not using Counter + ld a,$01 + ld [wMoveMissed],a ; initialize the move missed variable to true (it is set to false below if the move hits) + ld a,[hl] + cp a,COUNTER + ret z ; miss if the opponent's last selected move is Counter. + ld a,[de] + and a + ret z ; miss if the opponent's last selected move's Base Power is 0. +; check if the move the target last selected was Normal or Fighting type + inc de + ld a,[de] + and a ; normal type + jr z,.counterableType + cp a,FIGHTING + jr z,.counterableType +; if the move wasn't Normal or Fighting type, miss + xor a + ret +.counterableType + ld hl,wDamage + ld a,[hli] + or [hl] + ret z ; If we made it here, Counter still misses if the last move used in battle did no damage to its target. + ; wDamage is shared by both players, so Counter may strike back damage dealt by the Counter user itself + ; if the conditions meet, even though 99% of the times damage will come from the target. +; if it did damage, double it + ld a,[hl] + add a + ldd [hl],a + ld a,[hl] + adc a + ld [hl],a + jr nc,.noCarry +; damage is capped at 0xFFFF + ld a,$ff + ld [hli],a + ld [hl],a +.noCarry + xor a + ld [wMoveMissed],a + call MoveHitTest ; do the normal move hit test in addition to Counter's special rules + xor a + ret + +ApplyAttackToEnemyPokemon: + ld a,[wPlayerMoveEffect] + cp a,OHKO_EFFECT + jr z,ApplyDamageToEnemyPokemon + cp a,SUPER_FANG_EFFECT + jr z,.superFangEffect + cp a,SPECIAL_DAMAGE_EFFECT + jr z,.specialDamage + ld a,[wPlayerMovePower] + and a + jp z,ApplyAttackToEnemyPokemonDone ; no attack to apply if base power is 0 + jr ApplyDamageToEnemyPokemon +.superFangEffect +; set the damage to half the target's HP + ld hl,wEnemyMonHP + ld de,wDamage + ld a,[hli] + srl a + ld [de],a + inc de + ld b,a + ld a,[hl] + rr a + ld [de],a + or b + jr nz,ApplyDamageToEnemyPokemon +; make sure Super Fang's damage is always at least 1 + ld a,$01 + ld [de],a + jr ApplyDamageToEnemyPokemon +.specialDamage + ld hl,wBattleMonLevel + ld a,[hl] + ld b,a ; Seismic Toss deals damage equal to the user's level + ld a,[wPlayerMoveNum] + cp a,SEISMIC_TOSS + jr z,.storeDamage + cp a,NIGHT_SHADE + jr z,.storeDamage + ld b,SONICBOOM_DAMAGE ; 20 + cp a,SONICBOOM + jr z,.storeDamage + ld b,DRAGON_RAGE_DAMAGE ; 40 + cp a,DRAGON_RAGE + jr z,.storeDamage +; Psywave + ld a,[hl] + ld b,a + srl a + add b + ld b,a ; b = level * 1.5 +; loop until a random number in the range [1, b) is found +.loop + call BattleRandom + and a + jr z,.loop + cp b + jr nc,.loop + ld b,a +.storeDamage ; store damage value at b + ld hl,wDamage + xor a + ld [hli],a + ld a,b + ld [hl],a + +ApplyDamageToEnemyPokemon: + ld hl,wDamage + ld a,[hli] + ld b,a + ld a,[hl] + or b + jr z,ApplyAttackToEnemyPokemonDone ; we're done if damage is 0 + ld a,[wEnemyBattleStatus2] + bit HasSubstituteUp,a ; does the enemy have a substitute? + jp nz,AttackSubstitute +; subtract the damage from the pokemon's current HP +; also, save the current HP at wHPBarOldHP + ld a,[hld] + ld b,a + ld a,[wEnemyMonHP + 1] + ld [wHPBarOldHP],a + sub b + ld [wEnemyMonHP + 1],a + ld a,[hl] + ld b,a + ld a,[wEnemyMonHP] + ld [wHPBarOldHP+1],a + sbc b + ld [wEnemyMonHP],a + jr nc,.animateHpBar +; if more damage was done than the current HP, zero the HP and set the damage (wDamage) +; equal to how much HP the pokemon had before the attack + ld a,[wHPBarOldHP+1] + ld [hli],a + ld a,[wHPBarOldHP] + ld [hl],a + xor a + ld hl,wEnemyMonHP + ld [hli],a + ld [hl],a +.animateHpBar + ld hl,wEnemyMonMaxHP + ld a,[hli] + ld [wHPBarMaxHP+1],a + ld a,[hl] + ld [wHPBarMaxHP],a + ld hl,wEnemyMonHP + ld a,[hli] + ld [wHPBarNewHP+1],a + ld a,[hl] + ld [wHPBarNewHP],a + coord hl, 2, 2 + xor a + ld [wHPBarType],a + predef UpdateHPBar2 ; animate the HP bar shortening +ApplyAttackToEnemyPokemonDone: + jp DrawHUDsAndHPBars + +ApplyAttackToPlayerPokemon: + ld a,[wEnemyMoveEffect] + cp a,OHKO_EFFECT + jr z,ApplyDamageToPlayerPokemon + cp a,SUPER_FANG_EFFECT + jr z,.superFangEffect + cp a,SPECIAL_DAMAGE_EFFECT + jr z,.specialDamage + ld a,[wEnemyMovePower] + and a + jp z,ApplyAttackToPlayerPokemonDone + jr ApplyDamageToPlayerPokemon +.superFangEffect +; set the damage to half the target's HP + ld hl,wBattleMonHP + ld de,wDamage + ld a,[hli] + srl a + ld [de],a + inc de + ld b,a + ld a,[hl] + rr a + ld [de],a + or b + jr nz,ApplyDamageToPlayerPokemon +; make sure Super Fang's damage is always at least 1 + ld a,$01 + ld [de],a + jr ApplyDamageToPlayerPokemon +.specialDamage + ld hl,wEnemyMonLevel + ld a,[hl] + ld b,a + ld a,[wEnemyMoveNum] + cp a,SEISMIC_TOSS + jr z,.storeDamage + cp a,NIGHT_SHADE + jr z,.storeDamage + ld b,SONICBOOM_DAMAGE + cp a,SONICBOOM + jr z,.storeDamage + ld b,DRAGON_RAGE_DAMAGE + cp a,DRAGON_RAGE + jr z,.storeDamage +; Psywave + ld a,[hl] + ld b,a + srl a + add b + ld b,a ; b = attacker's level * 1.5 +; loop until a random number in the range [0, b) is found +; this differs from the range when the player attacks, which is [1, b) +; it's possible for the enemy to do 0 damage with Psywave, but the player always does at least 1 damage +.loop + call BattleRandom + cp b + jr nc,.loop + ld b,a +.storeDamage + ld hl,wDamage + xor a + ld [hli],a + ld a,b + ld [hl],a + +ApplyDamageToPlayerPokemon: + ld hl,wDamage + ld a,[hli] + ld b,a + ld a,[hl] + or b + jr z,ApplyAttackToPlayerPokemonDone ; we're done if damage is 0 + ld a,[wPlayerBattleStatus2] + bit HasSubstituteUp,a ; does the player have a substitute? + jp nz,AttackSubstitute +; subtract the damage from the pokemon's current HP +; also, save the current HP at wHPBarOldHP and the new HP at wHPBarNewHP + ld a,[hld] + ld b,a + ld a,[wBattleMonHP + 1] + ld [wHPBarOldHP],a + sub b + ld [wBattleMonHP + 1],a + ld [wHPBarNewHP],a + ld b,[hl] + ld a,[wBattleMonHP] + ld [wHPBarOldHP+1],a + sbc b + ld [wBattleMonHP],a + ld [wHPBarNewHP+1],a + jr nc,.animateHpBar +; if more damage was done than the current HP, zero the HP and set the damage (wDamage) +; equal to how much HP the pokemon had before the attack + ld a,[wHPBarOldHP+1] + ld [hli],a + ld a,[wHPBarOldHP] + ld [hl],a + xor a + ld hl,wBattleMonHP + ld [hli],a + ld [hl],a + ld hl,wHPBarNewHP + ld [hli],a + ld [hl],a +.animateHpBar + ld hl,wBattleMonMaxHP + ld a,[hli] + ld [wHPBarMaxHP+1],a + ld a,[hl] + ld [wHPBarMaxHP],a + coord hl, 10, 9 + ld a,$01 + ld [wHPBarType],a + predef UpdateHPBar2 ; animate the HP bar shortening +ApplyAttackToPlayerPokemonDone: + jp DrawHUDsAndHPBars + +AttackSubstitute: +; Unlike the two ApplyAttackToPokemon functions, Attack Substitute is shared by player and enemy. +; Self-confusion damage as well as Hi-Jump Kick and Jump Kick recoil cause a momentary turn swap before being applied. +; If the user has a Substitute up and would take damage because of that, +; damage will be applied to the other player's Substitute. +; Normal recoil such as from Double-Edge isn't affected by this glitch, +; because this function is never called in that case. + + ld hl,SubstituteTookDamageText + call PrintText +; values for player turn + ld de,wEnemySubstituteHP + ld bc,wEnemyBattleStatus2 + ld a,[H_WHOSETURN] + and a + jr z,.applyDamageToSubstitute +; values for enemy turn + ld de,wPlayerSubstituteHP + ld bc,wPlayerBattleStatus2 +.applyDamageToSubstitute + ld hl,wDamage + ld a,[hli] + and a + jr nz,.substituteBroke ; damage > 0xFF always breaks substitutes +; subtract damage from HP of substitute + ld a,[de] + sub [hl] + ld [de],a + ret nc +.substituteBroke +; If the target's Substitute breaks, wDamage isn't updated with the amount of HP +; the Substitute had before being attacked. + ld h,b + ld l,c + res HasSubstituteUp,[hl] ; unset the substitute bit + ld hl,SubstituteBrokeText + call PrintText +; flip whose turn it is for the next function call + ld a,[H_WHOSETURN] + xor a,$01 + ld [H_WHOSETURN],a + callab HideSubstituteShowMonAnim ; animate the substitute breaking +; flip the turn back to the way it was + ld a,[H_WHOSETURN] + xor a,$01 + ld [H_WHOSETURN],a + ld hl,wPlayerMoveEffect ; value for player's turn + and a + jr z,.nullifyEffect + ld hl,wEnemyMoveEffect ; value for enemy's turn +.nullifyEffect + xor a + ld [hl],a ; zero the effect of the attacker's move + jp DrawHUDsAndHPBars + +SubstituteTookDamageText: + TX_FAR _SubstituteTookDamageText + db "@" + +SubstituteBrokeText: + TX_FAR _SubstituteBrokeText + db "@" + +; this function raises the attack modifier of a pokemon using Rage when that pokemon is attacked +HandleBuildingRage: +; values for the player turn + ld hl,wEnemyBattleStatus2 + ld de,wEnemyMonStatMods + ld bc,wEnemyMoveNum + ld a,[H_WHOSETURN] + and a + jr z,.next +; values for the enemy turn + ld hl,wPlayerBattleStatus2 + ld de,wPlayerMonStatMods + ld bc,wPlayerMoveNum +.next + bit UsingRage,[hl] ; is the pokemon being attacked under the effect of Rage? + ret z ; return if not + ld a,[de] + cp a,$0d ; maximum stat modifier value + ret z ; return if attack modifier is already maxed + ld a,[H_WHOSETURN] + xor a,$01 ; flip turn for the stat modifier raising function + ld [H_WHOSETURN],a +; temporarily change the target pokemon's move to $00 and the effect to the one +; that causes the attack modifier to go up one stage + ld h,b + ld l,c + ld [hl],$00 ; null move number + inc hl + ld [hl],ATTACK_UP1_EFFECT + push hl + ld hl,BuildingRageText + call PrintText + call StatModifierUpEffect ; stat modifier raising function + pop hl + xor a + ldd [hl],a ; null move effect + ld a,RAGE + ld [hl],a ; restore the target pokemon's move number to Rage + ld a,[H_WHOSETURN] + xor a,$01 ; flip turn back to the way it was + ld [H_WHOSETURN],a + ret + +BuildingRageText: + TX_FAR _BuildingRageText + db "@" + +; copy last move for Mirror Move +; sets zero flag on failure and unsets zero flag on success +MirrorMoveCopyMove: +; Mirror Move makes use of ccf1 (wPlayerUsedMove) and ccf2 (wEnemyUsedMove) addresses, +; which are mainly used to print the "[Pokemon] used [Move]" text. +; Both are set to 0 whenever a new Pokemon is sent out +; ccf1 is also set to 0 whenever the player is fast asleep or frozen solid. +; ccf2 is also set to 0 whenever the enemy is fast asleep or frozen solid. + + ld a,[H_WHOSETURN] + and a +; values for player turn + ld a,[wEnemyUsedMove] + ld hl,wPlayerSelectedMove + ld de,wPlayerMoveNum + jr z,.next +; values for enemy turn + ld a,[wPlayerUsedMove] + ld de,wEnemyMoveNum + ld hl,wEnemySelectedMove +.next + ld [hl],a + cp a,MIRROR_MOVE ; did the target Pokemon last use Mirror Move, and miss? + jr z,.mirrorMoveFailed + and a ; has the target selected any move yet? + jr nz,ReloadMoveData +.mirrorMoveFailed + ld hl,MirrorMoveFailedText + call PrintText + xor a + ret + +MirrorMoveFailedText: + TX_FAR _MirrorMoveFailedText + db "@" + +; function used to reload move data for moves like Mirror Move and Metronome +ReloadMoveData: + ld [wd11e],a + dec a + ld hl,Moves + ld bc,MoveEnd - Moves + call AddNTimes + ld a,BANK(Moves) + call FarCopyData ; copy the move's stats + call IncrementMovePP +; the follow two function calls are used to reload the move name + call GetMoveName + call CopyStringToCF50 + ld a,$01 + and a + ret + +; function that picks a random move for metronome +MetronomePickMove: + xor a + ld [wAnimationType],a + ld a,METRONOME + call PlayMoveAnimation ; play Metronome's animation +; values for player turn + ld de,wPlayerMoveNum + ld hl,wPlayerSelectedMove + ld a,[H_WHOSETURN] + and a + jr z,.pickMoveLoop +; values for enemy turn + ld de,wEnemyMoveNum + ld hl,wEnemySelectedMove +; loop to pick a random number in the range [1, $a5) to be the move used by Metronome +.pickMoveLoop + call BattleRandom + and a + jr z,.pickMoveLoop + cp a,NUM_ATTACKS + 1 ; max normal move number + 1 (this is Struggle's move number) + jr nc,.pickMoveLoop + cp a,METRONOME + jr z,.pickMoveLoop + ld [hl],a + jr ReloadMoveData + +; this function increments the current move's PP +; it's used to prevent moves that run another move within the same turn +; (like Mirror Move and Metronome) from losing 2 PP +IncrementMovePP: + ld a,[H_WHOSETURN] + and a +; values for player turn + ld hl,wBattleMonPP + ld de,wPartyMon1PP + ld a,[wPlayerMoveListIndex] + jr z,.next +; values for enemy turn + ld hl,wEnemyMonPP + ld de,wEnemyMon1PP + ld a,[wEnemyMoveListIndex] +.next + ld b,$00 + ld c,a + add hl,bc + inc [hl] ; increment PP in the currently battling pokemon memory location + ld h,d + ld l,e + add hl,bc + ld a,[H_WHOSETURN] + and a + ld a,[wPlayerMonNumber] ; value for player turn + jr z,.updatePP + ld a,[wEnemyMonPartyPos] ; value for enemy turn +.updatePP + ld bc,wEnemyMon2 - wEnemyMon1 + call AddNTimes + inc [hl] ; increment PP in the party memory location + ret + +; function to adjust the base damage of an attack to account for type effectiveness +AdjustDamageForMoveType: +; values for player turn + ld hl,wBattleMonType + ld a,[hli] + ld b,a ; b = type 1 of attacker + ld c,[hl] ; c = type 2 of attacker + ld hl,wEnemyMonType + ld a,[hli] + ld d,a ; d = type 1 of defender + ld e,[hl] ; e = type 2 of defender + ld a,[wPlayerMoveType] + ld [wMoveType],a + ld a,[H_WHOSETURN] + and a + jr z,.next +; values for enemy turn + ld hl,wEnemyMonType + ld a,[hli] + ld b,a ; b = type 1 of attacker + ld c,[hl] ; c = type 2 of attacker + ld hl,wBattleMonType + ld a,[hli] + ld d,a ; d = type 1 of defender + ld e,[hl] ; e = type 2 of defender + ld a,[wEnemyMoveType] + ld [wMoveType],a +.next + ld a,[wMoveType] + cp b ; does the move type match type 1 of the attacker? + jr z,.sameTypeAttackBonus + cp c ; does the move type match type 2 of the attacker? + jr z,.sameTypeAttackBonus + jr .skipSameTypeAttackBonus +.sameTypeAttackBonus +; if the move type matches one of the attacker's types + ld hl,wDamage + 1 + ld a,[hld] + ld h,[hl] + ld l,a ; hl = damage + ld b,h + ld c,l ; bc = damage + srl b + rr c ; bc = floor(0.5 * damage) + add hl,bc ; hl = floor(1.5 * damage) +; store damage + ld a,h + ld [wDamage],a + ld a,l + ld [wDamage + 1],a + ld hl,wDamageMultipliers + set 7,[hl] +.skipSameTypeAttackBonus + ld a,[wMoveType] + ld b,a + ld hl,TypeEffects +.loop + ld a,[hli] ; a = "attacking type" of the current type pair + cp a,$ff + jr z,.done + cp b ; does move type match "attacking type"? + jr nz,.nextTypePair + ld a,[hl] ; a = "defending type" of the current type pair + cp d ; does type 1 of defender match "defending type"? + jr z,.matchingPairFound + cp e ; does type 2 of defender match "defending type"? + jr z,.matchingPairFound + jr .nextTypePair +.matchingPairFound +; if the move type matches the "attacking type" and one of the defender's types matches the "defending type" + push hl + push bc + inc hl + ld a,[wDamageMultipliers] + and a,$80 + ld b,a + ld a,[hl] ; a = damage multiplier + ld [H_MULTIPLIER],a + add b + ld [wDamageMultipliers],a + xor a + ld [H_MULTIPLICAND],a + ld hl,wDamage + ld a,[hli] + ld [H_MULTIPLICAND + 1],a + ld a,[hld] + ld [H_MULTIPLICAND + 2],a + call Multiply + ld a,10 + ld [H_DIVISOR],a + ld b,$04 + call Divide + ld a,[H_QUOTIENT + 2] + ld [hli],a + ld b,a + ld a,[H_QUOTIENT + 3] + ld [hl],a + or b ; is damage 0? + jr nz,.skipTypeImmunity +.typeImmunity +; if damage is 0, make the move miss +; this only occurs if a move that would do 2 or 3 damage is 0.25x effective against the target + inc a + ld [wMoveMissed],a +.skipTypeImmunity + pop bc + pop hl +.nextTypePair + inc hl + inc hl + jp .loop +.done + ret + +; function to tell how effective the type of an enemy attack is on the player's current pokemon +; this doesn't take into account the effects that dual types can have +; (e.g. 4x weakness / resistance, weaknesses and resistances canceling) +; the result is stored in [wTypeEffectiveness] +; ($05 is not very effective, $10 is neutral, $14 is super effective) +; as far is can tell, this is only used once in some AI code to help decide which move to use +AIGetTypeEffectiveness: + ld a,[wEnemyMoveType] + ld d,a ; d = type of enemy move + ld hl,wBattleMonType + ld b,[hl] ; b = type 1 of player's pokemon + inc hl + ld c,[hl] ; c = type 2 of player's pokemon + ld a,$10 + ld [wTypeEffectiveness],a ; initialize to neutral effectiveness + ld hl,TypeEffects +.loop + ld a,[hli] + cp a,$ff + ret z + cp d ; match the type of the move + jr nz,.nextTypePair1 + ld a,[hli] + cp b ; match with type 1 of pokemon + jr z,.done + cp c ; or match with type 2 of pokemon + jr z,.done + jr .nextTypePair2 +.nextTypePair1 + inc hl +.nextTypePair2 + inc hl + jr .loop +.done + ld a,[hl] + ld [wTypeEffectiveness],a ; store damage multiplier + ret + +INCLUDE "data/type_effects.asm" + +; some tests that need to pass for a move to hit +MoveHitTest: +; player's turn + ld hl,wEnemyBattleStatus1 + ld de,wPlayerMoveEffect + ld bc,wEnemyMonStatus + ld a,[H_WHOSETURN] + and a + jr z,.dreamEaterCheck +; enemy's turn + ld hl,wPlayerBattleStatus1 + ld de,wEnemyMoveEffect + ld bc,wBattleMonStatus +.dreamEaterCheck + ld a,[de] + cp a,DREAM_EATER_EFFECT + jr nz,.swiftCheck + ld a,[bc] + and a,SLP ; is the target pokemon sleeping? + jp z,.moveMissed +.swiftCheck + ld a,[de] + cp a,SWIFT_EFFECT + ret z ; Swift never misses (interestingly, Azure Heights lists this is a myth, but it appears to be true) + call CheckTargetSubstitute ; substitute check (note that this overwrites a) + jr z,.checkForDigOrFlyStatus +; this code is buggy. it's supposed to prevent HP draining moves from working on substitutes. +; since $7b79 overwrites a with either $00 or $01, it never works. + cp a,DRAIN_HP_EFFECT + jp z,.moveMissed + cp a,DREAM_EATER_EFFECT + jp z,.moveMissed +.checkForDigOrFlyStatus + bit Invulnerable,[hl] + jp nz,.moveMissed + ld a,[H_WHOSETURN] + and a + jr nz,.enemyTurn +.playerTurn +; this checks if the move effect is disallowed by mist + ld a,[wPlayerMoveEffect] + cp a,ATTACK_DOWN1_EFFECT + jr c,.skipEnemyMistCheck + cp a,HAZE_EFFECT + 1 + jr c,.enemyMistCheck + cp a,ATTACK_DOWN2_EFFECT + jr c,.skipEnemyMistCheck + cp a,REFLECT_EFFECT + 1 + jr c,.enemyMistCheck + jr .skipEnemyMistCheck +.enemyMistCheck +; if move effect is from $12 to $19 inclusive or $3a to $41 inclusive +; i.e. the following moves +; GROWL, TAIL WHIP, LEER, STRING SHOT, SAND-ATTACK, SMOKESCREEN, KINESIS, +; FLASH, CONVERSION*, HAZE*, SCREECH, LIGHT SCREEN*, REFLECT* +; the moves that are marked with an asterisk are not affected since this +; function is not called when those moves are used + ld a,[wEnemyBattleStatus2] + bit ProtectedByMist,a ; is mon protected by mist? + jp nz,.moveMissed +.skipEnemyMistCheck + ld a,[wPlayerBattleStatus2] + bit UsingXAccuracy,a ; is the player using X Accuracy? + ret nz ; if so, always hit regardless of accuracy/evasion + jr .calcHitChance +.enemyTurn + ld a,[wEnemyMoveEffect] + cp a,ATTACK_DOWN1_EFFECT + jr c,.skipPlayerMistCheck + cp a,HAZE_EFFECT + 1 + jr c,.playerMistCheck + cp a,ATTACK_DOWN2_EFFECT + jr c,.skipPlayerMistCheck + cp a,REFLECT_EFFECT + 1 + jr c,.playerMistCheck + jr .skipPlayerMistCheck +.playerMistCheck +; similar to enemy mist check + ld a,[wPlayerBattleStatus2] + bit ProtectedByMist,a ; is mon protected by mist? + jp nz,.moveMissed +.skipPlayerMistCheck + ld a,[wEnemyBattleStatus2] + bit UsingXAccuracy,a ; is the enemy using X Accuracy? + ret nz ; if so, always hit regardless of accuracy/evasion +.calcHitChance + call CalcHitChance ; scale the move accuracy according to attacker's accuracy and target's evasion + ld a,[wPlayerMoveAccuracy] + ld b,a + ld a,[H_WHOSETURN] + and a + jr z,.doAccuracyCheck + ld a,[wEnemyMoveAccuracy] + ld b,a +.doAccuracyCheck +; if the random number generated is greater than or equal to the scaled accuracy, the move misses +; note that this means that even the highest accuracy is still just a 255/256 chance, not 100% + call BattleRandom + cp b + jr nc,.moveMissed + ret +.moveMissed + xor a + ld hl,wDamage ; zero the damage + ld [hli],a + ld [hl],a + inc a + ld [wMoveMissed],a + ld a,[H_WHOSETURN] + and a + jr z,.playerTurn2 +.enemyTurn2 + ld hl,wEnemyBattleStatus1 + res UsingTrappingMove,[hl] ; end multi-turn attack e.g. wrap + ret +.playerTurn2 + ld hl,wPlayerBattleStatus1 + res UsingTrappingMove,[hl] ; end multi-turn attack e.g. wrap + ret + +; values for player turn +CalcHitChance: + ld hl,wPlayerMoveAccuracy + ld a,[H_WHOSETURN] + and a + ld a,[wPlayerMonAccuracyMod] + ld b,a + ld a,[wEnemyMonEvasionMod] + ld c,a + jr z,.next +; values for enemy turn + ld hl,wEnemyMoveAccuracy + ld a,[wEnemyMonAccuracyMod] + ld b,a + ld a,[wPlayerMonEvasionMod] + ld c,a +.next + ld a,$0e + sub c + ld c,a ; c = 14 - EVASIONMOD (this "reflects" the value over 7, so that an increase in the target's evasion + ; decreases the hit chance instead of increasing the hit chance) +; zero the high bytes of the multiplicand + xor a + ld [H_MULTIPLICAND],a + ld [H_MULTIPLICAND + 1],a + ld a,[hl] + ld [H_MULTIPLICAND + 2],a ; set multiplicand to move accuracy + push hl + ld d,$02 ; loop has two iterations +; loop to do the calculations, the first iteration multiplies by the accuracy ratio and +; the second iteration multiplies by the evasion ratio +.loop + push bc + ld hl, StatModifierRatios ; stat modifier ratios + dec b + sla b + ld c,b + ld b,$00 + add hl,bc ; hl = address of stat modifier ratio + pop bc + ld a,[hli] + ld [H_MULTIPLIER],a ; set multiplier to the numerator of the ratio + call Multiply + ld a,[hl] + ld [H_DIVISOR],a ; set divisor to the the denominator of the ratio + ; (the dividend is the product of the previous multiplication) + ld b,$04 ; number of bytes in the dividend + call Divide + ld a,[H_QUOTIENT + 3] + ld b,a + ld a,[H_QUOTIENT + 2] + or b + jp nz,.nextCalculation +; make sure the result is always at least one + ld [H_QUOTIENT + 2],a + ld a,$01 + ld [H_QUOTIENT + 3],a +.nextCalculation + ld b,c + dec d + jr nz,.loop + ld a,[H_QUOTIENT + 2] + and a ; is the calculated hit chance over 0xFF? + ld a,[H_QUOTIENT + 3] + jr z,.storeAccuracy +; if calculated hit chance over 0xFF + ld a,$ff ; set the hit chance to 0xFF +.storeAccuracy + pop hl + ld [hl],a ; store the hit chance in the move accuracy variable + ret + +; multiplies damage by a random percentage from ~85% to 100% +RandomizeDamage: + ld hl, wDamage + ld a, [hli] + and a + jr nz, .DamageGreaterThanOne + ld a, [hl] + cp 2 + ret c ; return if damage is equal to 0 or 1 +.DamageGreaterThanOne + xor a + ld [H_MULTIPLICAND], a + dec hl + ld a, [hli] + ld [H_MULTIPLICAND + 1], a + ld a, [hl] + ld [H_MULTIPLICAND + 2], a +; loop until a random number greater than or equal to 217 is generated +.loop + call BattleRandom + rrca + cp 217 + jr c, .loop + ld [H_MULTIPLIER], a + call Multiply ; multiply damage by the random number, which is in the range [217, 255] + ld a, 255 + ld [H_DIVISOR], a + ld b, $4 + call Divide ; divide the result by 255 +; store the modified damage + ld a, [H_QUOTIENT + 2] + ld hl, wDamage + ld [hli], a + ld a, [H_QUOTIENT + 3] + ld [hl], a + ret + +; for more detailed commentary, see equivalent function for player side (ExecutePlayerMove) +ExecuteEnemyMove: + ld a, [wEnemySelectedMove] + inc a + jp z, ExecuteEnemyMoveDone + call PrintGhostText + jp z, ExecuteEnemyMoveDone + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr nz, .executeEnemyMove + ld b, $1 + ld a, [wSerialExchangeNybbleReceiveData] + cp LINKBATTLE_STRUGGLE + jr z, .executeEnemyMove + cp 4 + ret nc +.executeEnemyMove + ld hl, wAILayer2Encouragement + inc [hl] + xor a + ld [wMoveMissed], a + ld [wMoveDidntMiss], a + ld a, $a + ld [wDamageMultipliers], a + call CheckEnemyStatusConditions + jr nz, .enemyHasNoSpecialConditions + jp hl +.enemyHasNoSpecialConditions + ld hl, wEnemyBattleStatus1 + bit ChargingUp, [hl] ; is the enemy charging up for attack? + jr nz, EnemyCanExecuteChargingMove ; if so, jump + call GetCurrentMove + +CheckIfEnemyNeedsToChargeUp: + ld a, [wEnemyMoveEffect] + cp CHARGE_EFFECT + jp z, JumpMoveEffect + cp FLY_EFFECT + jp z, JumpMoveEffect + jr EnemyCanExecuteMove +EnemyCanExecuteChargingMove: + ld hl, wEnemyBattleStatus1 + res ChargingUp, [hl] ; no longer charging up for attack + res Invulnerable, [hl] ; no longer invulnerable to typical attacks + ld a, [wEnemyMoveNum] + ld [wd0b5], a + ld a, BANK(MoveNames) + ld [wPredefBank], a + ld a, MOVE_NAME + ld [wNameListType], a + call GetName + ld de, wcd6d + call CopyStringToCF50 +EnemyCanExecuteMove: + xor a + ld [wMonIsDisobedient], a + call PrintMonName1Text + ld a, [wEnemyMoveEffect] + ld hl, ResidualEffects1 + ld de, $1 + call IsInArray + jp c, JumpMoveEffect + ld a, [wEnemyMoveEffect] + ld hl, SpecialEffectsCont + ld de, $1 + call IsInArray + call c, JumpMoveEffect +EnemyCalcMoveDamage: + call SwapPlayerAndEnemyLevels + ld a, [wEnemyMoveEffect] + ld hl, SetDamageEffects + ld de, $1 + call IsInArray + jp c, EnemyMoveHitTest + call CriticalHitTest + call HandleCounterMove + jr z, handleIfEnemyMoveMissed + call SwapPlayerAndEnemyLevels + call GetDamageVarsForEnemyAttack + call SwapPlayerAndEnemyLevels + call CalculateDamage + jp z, EnemyCheckIfFlyOrChargeEffect + call AdjustDamageForMoveType + call RandomizeDamage + +EnemyMoveHitTest: + call MoveHitTest +handleIfEnemyMoveMissed: + ld a, [wMoveMissed] + and a + jr z, .moveDidNotMiss + ld a, [wEnemyMoveEffect] + cp EXPLODE_EFFECT + jr z, handleExplosionMiss + jr EnemyCheckIfFlyOrChargeEffect +.moveDidNotMiss + call SwapPlayerAndEnemyLevels + +GetEnemyAnimationType: + ld a, [wEnemyMoveEffect] + and a + ld a, $1 + jr z, playEnemyMoveAnimation + ld a, $2 + jr playEnemyMoveAnimation +handleExplosionMiss: + call SwapPlayerAndEnemyLevels + xor a +playEnemyMoveAnimation: + push af + ld a, [wEnemyBattleStatus2] + bit HasSubstituteUp, a ; does mon have a substitute? + ld hl, HideSubstituteShowMonAnim + ld b, BANK(HideSubstituteShowMonAnim) + call nz, Bankswitch + pop af + ld [wAnimationType], a + ld a, [wEnemyMoveNum] + call PlayMoveAnimation + call HandleExplodingAnimation + call DrawEnemyHUDAndHPBar + ld a, [wEnemyBattleStatus2] + bit HasSubstituteUp, a ; does mon have a substitute? + ld hl, ReshowSubstituteAnim + ld b, BANK(ReshowSubstituteAnim) + call nz, Bankswitch ; slide the substitute's sprite out + jr EnemyCheckIfMirrorMoveEffect + +EnemyCheckIfFlyOrChargeEffect: + call SwapPlayerAndEnemyLevels + ld c, 30 + call DelayFrames + ld a, [wEnemyMoveEffect] + cp FLY_EFFECT + jr z, .playAnim + cp CHARGE_EFFECT + jr z, .playAnim + jr EnemyCheckIfMirrorMoveEffect +.playAnim + xor a + ld [wAnimationType], a + ld a,STATUS_AFFECTED_ANIM + call PlayMoveAnimation +EnemyCheckIfMirrorMoveEffect: + ld a, [wEnemyMoveEffect] + cp MIRROR_MOVE_EFFECT + jr nz, .notMirrorMoveEffect + call MirrorMoveCopyMove + jp z, ExecuteEnemyMoveDone + jp CheckIfEnemyNeedsToChargeUp +.notMirrorMoveEffect + cp METRONOME_EFFECT + jr nz, .notMetronomeEffect + call MetronomePickMove + jp CheckIfEnemyNeedsToChargeUp +.notMetronomeEffect + ld a, [wEnemyMoveEffect] + ld hl, ResidualEffects2 + ld de, $1 + call IsInArray + jp c, JumpMoveEffect + ld a, [wMoveMissed] + and a + jr z, .moveDidNotMiss + call PrintMoveFailureText + ld a, [wEnemyMoveEffect] + cp EXPLODE_EFFECT + jr z, .handleExplosionMiss + jp ExecuteEnemyMoveDone +.moveDidNotMiss + call ApplyAttackToPlayerPokemon + call PrintCriticalOHKOText + callab DisplayEffectiveness + ld a, 1 + ld [wMoveDidntMiss], a +.handleExplosionMiss + ld a, [wEnemyMoveEffect] + ld hl, AlwaysHappenSideEffects + ld de, $1 + call IsInArray + call c, JumpMoveEffect + ld hl, wBattleMonHP + ld a, [hli] + ld b, [hl] + or b + ret z + call HandleBuildingRage + ld hl, wEnemyBattleStatus1 + bit AttackingMultipleTimes, [hl] ; is mon hitting multiple times? (example: double kick) + jr z, .notMultiHitMove + push hl + ld hl, wEnemyNumAttacksLeft + dec [hl] + pop hl + jp nz, GetEnemyAnimationType + res AttackingMultipleTimes, [hl] ; mon is no longer hitting multiple times + ld hl, HitXTimesText + call PrintText + xor a + ld [wEnemyNumHits], a +.notMultiHitMove + ld a, [wEnemyMoveEffect] + and a + jr z, ExecuteEnemyMoveDone + ld hl, SpecialEffects + ld de, $1 + call IsInArray + call nc, JumpMoveEffect + jr ExecuteEnemyMoveDone + +HitXTimesText: + TX_FAR _HitXTimesText + db "@" + +ExecuteEnemyMoveDone: + ld b, $1 + ret + +; checks for various status conditions affecting the enemy mon +; stores whether the mon cannot use a move this turn in Z flag +CheckEnemyStatusConditions: + ld hl, wEnemyMonStatus + ld a, [hl] + and SLP ; sleep mask + jr z, .checkIfFrozen + dec a ; decrement number of turns left + ld [wEnemyMonStatus], a + and a + jr z, .wokeUp ; if the number of turns hit 0, wake up + ld hl, FastAsleepText + call PrintText + xor a + ld [wAnimationType], a + ld a,SLP_ANIM + call PlayMoveAnimation + jr .sleepDone +.wokeUp + ld hl, WokeUpText + call PrintText +.sleepDone + xor a + ld [wEnemyUsedMove], a + ld hl, ExecuteEnemyMoveDone ; enemy can't move this turn + jp .enemyReturnToHL +.checkIfFrozen + bit FRZ, [hl] + jr z, .checkIfTrapped + ld hl, IsFrozenText + call PrintText + xor a + ld [wEnemyUsedMove], a + ld hl, ExecuteEnemyMoveDone ; enemy can't move this turn + jp .enemyReturnToHL +.checkIfTrapped + ld a, [wPlayerBattleStatus1] + bit UsingTrappingMove, a ; is the player using a multi-turn attack like warp + jp z, .checkIfFlinched + ld hl, CantMoveText + call PrintText + ld hl, ExecuteEnemyMoveDone ; enemy can't move this turn + jp .enemyReturnToHL +.checkIfFlinched + ld hl, wEnemyBattleStatus1 + bit Flinched, [hl] ; check if enemy mon flinched + jp z, .checkIfMustRecharge + res Flinched, [hl] + ld hl, FlinchedText + call PrintText + ld hl, ExecuteEnemyMoveDone ; enemy can't move this turn + jp .enemyReturnToHL +.checkIfMustRecharge + ld hl, wEnemyBattleStatus2 + bit NeedsToRecharge, [hl] ; check if enemy mon has to recharge after using a move + jr z, .checkIfAnyMoveDisabled + res NeedsToRecharge, [hl] + ld hl, MustRechargeText + call PrintText + ld hl, ExecuteEnemyMoveDone ; enemy can't move this turn + jp .enemyReturnToHL +.checkIfAnyMoveDisabled + ld hl, wEnemyDisabledMove + ld a, [hl] + and a + jr z, .checkIfConfused + dec a ; decrement disable counter + ld [hl], a + and $f ; did disable counter hit 0? + jr nz, .checkIfConfused + ld [hl], a + ld [wEnemyDisabledMoveNumber], a + ld hl, DisabledNoMoreText + call PrintText +.checkIfConfused + ld a, [wEnemyBattleStatus1] + add a ; check if enemy mon is confused + jp nc, .checkIfTriedToUseDisabledMove + ld hl, wEnemyConfusedCounter + dec [hl] + jr nz, .isConfused + ld hl, wEnemyBattleStatus1 + res Confused, [hl] ; if confused counter hit 0, reset confusion status + ld hl, ConfusedNoMoreText + call PrintText + jp .checkIfTriedToUseDisabledMove +.isConfused + ld hl, IsConfusedText + call PrintText + xor a + ld [wAnimationType], a + ld a,CONF_ANIM + call PlayMoveAnimation + call BattleRandom + cp $80 + jr c, .checkIfTriedToUseDisabledMove + ld hl, wEnemyBattleStatus1 + ld a, [hl] + and 1 << Confused ; if mon hurts itself, clear every other status from wEnemyBattleStatus1 + ld [hl], a + ld hl, HurtItselfText + call PrintText + ld hl, wBattleMonDefense + ld a, [hli] + push af + ld a, [hld] + push af + ld a, [wEnemyMonDefense] + ld [hli], a + ld a, [wEnemyMonDefense + 1] + ld [hl], a + ld hl, wEnemyMoveEffect + push hl + ld a, [hl] + push af + xor a + ld [hli], a + ld [wCriticalHitOrOHKO], a + ld a, 40 + ld [hli], a + xor a + ld [hl], a + call GetDamageVarsForEnemyAttack + call CalculateDamage + pop af + pop hl + ld [hl], a + ld hl, wBattleMonDefense + 1 + pop af + ld [hld], a + pop af + ld [hl], a + xor a + ld [wAnimationType], a + ld [H_WHOSETURN], a + ld a, POUND + call PlayMoveAnimation + ld a, $1 + ld [H_WHOSETURN], a + call ApplyDamageToEnemyPokemon + jr .monHurtItselfOrFullyParalysed +.checkIfTriedToUseDisabledMove +; prevents a disabled move that was selected before being disabled from being used + ld a, [wEnemyDisabledMoveNumber] + and a + jr z, .checkIfParalysed + ld hl, wEnemySelectedMove + cp [hl] + jr nz, .checkIfParalysed + call PrintMoveIsDisabledText + ld hl, ExecuteEnemyMoveDone ; if a disabled move was somehow selected, player can't move this turn + jp .enemyReturnToHL +.checkIfParalysed + ld hl, wEnemyMonStatus + bit PAR, [hl] + jr z, .checkIfUsingBide + call BattleRandom + cp $3f ; 25% to be fully paralysed + jr nc, .checkIfUsingBide + ld hl, FullyParalyzedText + call PrintText +.monHurtItselfOrFullyParalysed + ld hl, wEnemyBattleStatus1 + ld a, [hl] + ; clear bide, thrashing about, charging up, and multi-turn moves such as warp + and $ff ^ ((1 << StoringEnergy) | (1 << ThrashingAbout) | (1 << ChargingUp) | (1 << UsingTrappingMove)) + ld [hl], a + ld a, [wEnemyMoveEffect] + cp FLY_EFFECT + jr z, .flyOrChargeEffect + cp CHARGE_EFFECT + jr z, .flyOrChargeEffect + jr .notFlyOrChargeEffect +.flyOrChargeEffect + xor a + ld [wAnimationType], a + ld a, STATUS_AFFECTED_ANIM + call PlayMoveAnimation +.notFlyOrChargeEffect + ld hl, ExecuteEnemyMoveDone + jp .enemyReturnToHL ; if using a two-turn move, enemy needs to recharge the first turn +.checkIfUsingBide + ld hl, wEnemyBattleStatus1 + bit StoringEnergy, [hl] ; is mon using bide? + jr z, .checkIfThrashingAbout + xor a + ld [wEnemyMoveNum], a + ld hl, wDamage + ld a, [hli] + ld b, a + ld c, [hl] + ld hl, wEnemyBideAccumulatedDamage + 1 + ld a, [hl] + add c ; accumulate damage taken + ld [hld], a + ld a, [hl] + adc b + ld [hl], a + ld hl, wEnemyNumAttacksLeft + dec [hl] ; did Bide counter hit 0? + jr z, .unleashEnergy + ld hl, ExecuteEnemyMoveDone + jp .enemyReturnToHL ; unless mon unleashes energy, can't move this turn +.unleashEnergy + ld hl, wEnemyBattleStatus1 + res StoringEnergy, [hl] ; not using bide any more + ld hl, UnleashedEnergyText + call PrintText + ld a, $1 + ld [wEnemyMovePower], a + ld hl, wEnemyBideAccumulatedDamage + 1 + ld a, [hld] + add a + ld b, a + ld [wDamage + 1], a + ld a, [hl] + rl a ; double the damage + ld [wDamage], a + or b + jr nz, .next + ld a, $1 + ld [wMoveMissed], a +.next + xor a + ld [hli], a + ld [hl], a + ld a, BIDE + ld [wEnemyMoveNum], a + call SwapPlayerAndEnemyLevels + ld hl, handleIfEnemyMoveMissed ; skip damage calculation, DecrementPP and MoveHitTest + jp .enemyReturnToHL +.checkIfThrashingAbout + bit ThrashingAbout, [hl] ; is mon using thrash or petal dance? + jr z, .checkIfUsingMultiturnMove + ld a, THRASH + ld [wEnemyMoveNum], a + ld hl, ThrashingAboutText + call PrintText + ld hl, wEnemyNumAttacksLeft + dec [hl] ; did Thrashing About counter hit 0? + ld hl, EnemyCalcMoveDamage ; skip DecrementPP + jp nz, .enemyReturnToHL + push hl + ld hl, wEnemyBattleStatus1 + res ThrashingAbout, [hl] ; mon is no longer using thrash or petal dance + set Confused, [hl] ; mon is now confused + call BattleRandom + and $3 + inc a + inc a ; confused for 2-5 turns + ld [wEnemyConfusedCounter], a + pop hl ; skip DecrementPP + jp .enemyReturnToHL +.checkIfUsingMultiturnMove + bit UsingTrappingMove, [hl] ; is mon using multi-turn move? + jp z, .checkIfUsingRage + ld hl, AttackContinuesText + call PrintText + ld hl, wEnemyNumAttacksLeft + dec [hl] ; did multi-turn move end? + ld hl, GetEnemyAnimationType ; if it didn't, skip damage calculation (deal damage equal to last hit), + ; DecrementPP and MoveHitTest + jp nz, .enemyReturnToHL + jp .enemyReturnToHL +.checkIfUsingRage + ld a, [wEnemyBattleStatus2] + bit UsingRage, a ; is mon using rage? + jp z, .checkEnemyStatusConditionsDone ; if we made it this far, mon can move normally this turn + ld a, RAGE + ld [wd11e], a + call GetMoveName + call CopyStringToCF50 + xor a + ld [wEnemyMoveEffect], a + ld hl, EnemyCanExecuteMove + jp .enemyReturnToHL +.enemyReturnToHL + xor a ; set Z flag + ret +.checkEnemyStatusConditionsDone + ld a, $1 + and a ; clear Z flag + ret + +GetCurrentMove: + ld a, [H_WHOSETURN] + and a + jp z, .player + ld de, wEnemyMoveNum + ld a, [wEnemySelectedMove] + jr .selected +.player + ld de, wPlayerMoveNum + ld a, [wFlags_D733] + bit BIT_TEST_BATTLE, a + ld a, [wTestBattlePlayerSelectedMove] + jr nz, .selected + ld a, [wPlayerSelectedMove] +.selected + ld [wd0b5], a + dec a + ld hl, Moves + ld bc, MoveEnd - Moves + call AddNTimes + ld a, BANK(Moves) + call FarCopyData + + ld a, BANK(MoveNames) + ld [wPredefBank], a + ld a, MOVE_NAME + ld [wNameListType], a + call GetName + ld de, wcd6d + jp CopyStringToCF50 + +LoadEnemyMonData: + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jp z, LoadEnemyMonFromParty + ld a, [wEnemyMonSpecies2] + ld [wEnemyMonSpecies], a + ld [wd0b5], a + call GetMonHeader + ld a, [wEnemyBattleStatus3] + bit Transformed, a ; is enemy mon transformed? + ld hl, wTransformedEnemyMonOriginalDVs ; original DVs before transforming + ld a, [hli] + ld b, [hl] + jr nz, .storeDVs + ld a, [wIsInBattle] + cp $2 ; is it a trainer battle? +; fixed DVs for trainer mon + ld a, $98 + ld b, $88 + jr z, .storeDVs +; random DVs for wild mon + call BattleRandom + ld b, a + call BattleRandom +.storeDVs + ld hl, wEnemyMonDVs + ld [hli], a + ld [hl], b + ld de, wEnemyMonLevel + ld a, [wCurEnemyLVL] + ld [de], a + inc de + ld b, $0 + ld hl, wEnemyMonHP + push hl + call CalcStats + pop hl + ld a, [wIsInBattle] + cp $2 ; is it a trainer battle? + jr z, .copyHPAndStatusFromPartyData + ld a, [wEnemyBattleStatus3] + bit Transformed, a ; is enemy mon transformed? + jr nz, .copyTypes ; if transformed, jump +; if it's a wild mon and not transformed, init the current HP to max HP and the status to 0 + ld a, [wEnemyMonMaxHP] + ld [hli], a + ld a, [wEnemyMonMaxHP+1] + ld [hli], a + xor a + inc hl + ld [hl], a ; init status to 0 + jr .copyTypes +; if it's a trainer mon, copy the HP and status from the enemy party data +.copyHPAndStatusFromPartyData + ld hl, wEnemyMon1HP + ld a, [wWhichPokemon] + ld bc, wEnemyMon2 - wEnemyMon1 + call AddNTimes + ld a, [hli] + ld [wEnemyMonHP], a + ld a, [hli] + ld [wEnemyMonHP + 1], a + ld a, [wWhichPokemon] + ld [wEnemyMonPartyPos], a + inc hl + ld a, [hl] + ld [wEnemyMonStatus], a + jr .copyTypes +.copyTypes + ld hl, wMonHTypes + ld de, wEnemyMonType + ld a, [hli] ; copy type 1 + ld [de], a + inc de + ld a, [hli] ; copy type 2 + ld [de], a + inc de + ld a, [hli] ; copy catch rate + ld [de], a + inc de + ld a, [wIsInBattle] + cp $2 ; is it a trainer battle? + jr nz, .copyStandardMoves +; if it's a trainer battle, copy moves from enemy party data + ld hl, wEnemyMon1Moves + ld a, [wWhichPokemon] + ld bc, wEnemyMon2 - wEnemyMon1 + call AddNTimes + ld bc, NUM_MOVES + call CopyData + jr .loadMovePPs +.copyStandardMoves +; for a wild mon, first copy default moves from the mon header + ld hl, wMonHMoves + ld a, [hli] + ld [de], a + inc de + ld a, [hli] + ld [de], a + inc de + ld a, [hli] + ld [de], a + inc de + ld a, [hl] + ld [de], a + dec de + dec de + dec de + xor a + ld [wLearningMovesFromDayCare], a + predef WriteMonMoves ; get moves based on current level +.loadMovePPs + ld hl, wEnemyMonMoves + ld de, wEnemyMonPP - 1 + predef LoadMovePPs + ld hl, wMonHBaseStats + ld de, wEnemyMonBaseStats + ld b, NUM_STATS +.copyBaseStatsLoop + ld a, [hli] + ld [de], a + inc de + dec b + jr nz, .copyBaseStatsLoop + ld hl, wMonHCatchRate + ld a, [hli] + ld [de], a + inc de + ld a, [hl] ; base exp + ld [de], a + ld a, [wEnemyMonSpecies2] + ld [wd11e], a + call GetMonName + ld hl, wcd6d + ld de, wEnemyMonNick + ld bc, NAME_LENGTH + call CopyData + ld a, [wEnemyMonSpecies2] + ld [wd11e], a + predef IndexToPokedex + ld a, [wd11e] + dec a + ld c, a + ld b, FLAG_SET + ld hl, wPokedexSeen + predef FlagActionPredef ; mark this mon as seen in the pokedex + ld hl, wEnemyMonLevel + ld de, wEnemyMonUnmodifiedLevel + ld bc, 1 + NUM_STATS * 2 + call CopyData + ld a, $7 ; default stat mod + ld b, NUM_STAT_MODS ; number of stat mods + ld hl, wEnemyMonStatMods +.statModLoop + ld [hli], a + dec b + jr nz, .statModLoop + ret + +; calls BattleTransition to show the battle transition animation and initializes some battle variables +DoBattleTransitionAndInitBattleVariables: + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr nz, .next +; link battle + xor a + ld [wMenuJoypadPollCount], a + callab DisplayLinkBattleVersusTextBox + ld a, $1 + ld [wUpdateSpritesEnabled], a + call ClearScreen +.next + call DelayFrame + predef BattleTransition + callab LoadHudAndHpBarAndStatusTilePatterns + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + ld a, $ff + ld [wUpdateSpritesEnabled], a + call ClearSprites + call ClearScreen + xor a + ld [H_AUTOBGTRANSFERENABLED], a + ld [hWY], a + ld [rWY], a + ld [hTilesetType], a + ld hl, wPlayerStatsToDouble + ld [hli], a + ld [hli], a + ld [hli], a + ld [hli], a + ld [hl], a + ld [wPlayerDisabledMove], a + ret + +; swaps the level values of the BattleMon and EnemyMon structs +SwapPlayerAndEnemyLevels: + push bc + ld a, [wBattleMonLevel] + ld b, a + ld a, [wEnemyMonLevel] + ld [wBattleMonLevel], a + ld a, b + ld [wEnemyMonLevel], a + pop bc + ret + +; loads either red back pic or old man back pic +; also writes OAM data and loads tile patterns for the Red or Old Man back sprite's head +; (for use when scrolling the player sprite and enemy's silhouettes on screen) +LoadPlayerBackPic: + ld a, [wBattleType] + dec a ; is it the old man tutorial? + ld de, RedPicBack + jr nz, .next + ld de, OldManPic +.next + ld a, BANK(RedPicBack) + call UncompressSpriteFromDE + predef ScaleSpriteByTwo + ld hl, wOAMBuffer + xor a + ld [hOAMTile], a ; initial tile number + ld b, $7 ; 7 columns + ld e, $a0 ; X for the left-most column +.loop ; each loop iteration writes 3 OAM entries in a vertical column + ld c, $3 ; 3 tiles per column + ld d, $38 ; Y for the top of each column +.innerLoop ; each loop iteration writes 1 OAM entry in the column + ld [hl], d ; OAM Y + inc hl + ld [hl], e ; OAM X + ld a, $8 ; height of tile + add d ; increase Y by height of tile + ld d, a + inc hl + ld a, [hOAMTile] + ld [hli], a ; OAM tile number + inc a ; increment tile number + ld [hOAMTile], a + inc hl + dec c + jr nz, .innerLoop + ld a, [hOAMTile] + add $4 ; increase tile number by 4 + ld [hOAMTile], a + ld a, $8 ; width of tile + add e ; increase X by width of tile + ld e, a + dec b + jr nz, .loop + ld de, vBackPic + call InterlaceMergeSpriteBuffers + ld a, $a + ld [$0], a + xor a + ld [$4000], a + ld hl, vSprites + ld de, sSpriteBuffer1 + ld a, [H_LOADEDROMBANK] + ld b, a + ld c, 7 * 7 + call CopyVideoData + xor a + ld [$0], a + ld a, $31 + ld [hStartTileID], a + coord hl, 1, 5 + predef_jump CopyUncompressedPicToTilemap + +; does nothing since no stats are ever selected (barring glitches) +DoubleOrHalveSelectedStats: + callab DoubleSelectedStats + jpab HalveSelectedStats + +ScrollTrainerPicAfterBattle: + jpab _ScrollTrainerPicAfterBattle + +ApplyBurnAndParalysisPenaltiesToPlayer: + ld a, $1 + jr ApplyBurnAndParalysisPenalties + +ApplyBurnAndParalysisPenaltiesToEnemy: + xor a + +ApplyBurnAndParalysisPenalties: + ld [H_WHOSETURN], a + call QuarterSpeedDueToParalysis + jp HalveAttackDueToBurn + +QuarterSpeedDueToParalysis: + ld a, [H_WHOSETURN] + and a + jr z, .playerTurn +.enemyTurn ; quarter the player's speed + ld a, [wBattleMonStatus] + and 1 << PAR + ret z ; return if player not paralysed + ld hl, wBattleMonSpeed + 1 + ld a, [hld] + ld b, a + ld a, [hl] + srl a + rr b + srl a + rr b + ld [hli], a + or b + jr nz, .storePlayerSpeed + ld b, 1 ; give the player a minimum of at least one speed point +.storePlayerSpeed + ld [hl], b + ret +.playerTurn ; quarter the enemy's speed + ld a, [wEnemyMonStatus] + and 1 << PAR + ret z ; return if enemy not paralysed + ld hl, wEnemyMonSpeed + 1 + ld a, [hld] + ld b, a + ld a, [hl] + srl a + rr b + srl a + rr b + ld [hli], a + or b + jr nz, .storeEnemySpeed + ld b, 1 ; give the enemy a minimum of at least one speed point +.storeEnemySpeed + ld [hl], b + ret + +HalveAttackDueToBurn: + ld a, [H_WHOSETURN] + and a + jr z, .playerTurn +.enemyTurn ; halve the player's attack + ld a, [wBattleMonStatus] + and 1 << BRN + ret z ; return if player not burnt + ld hl, wBattleMonAttack + 1 + ld a, [hld] + ld b, a + ld a, [hl] + srl a + rr b + ld [hli], a + or b + jr nz, .storePlayerAttack + ld b, 1 ; give the player a minimum of at least one attack point +.storePlayerAttack + ld [hl], b + ret +.playerTurn ; halve the enemy's attack + ld a, [wEnemyMonStatus] + and 1 << BRN + ret z ; return if enemy not burnt + ld hl, wEnemyMonAttack + 1 + ld a, [hld] + ld b, a + ld a, [hl] + srl a + rr b + ld [hli], a + or b + jr nz, .storeEnemyAttack + ld b, 1 ; give the enemy a minimum of at least one attack point +.storeEnemyAttack + ld [hl], b + ret + +CalculateModifiedStats: + ld c, 0 +.loop + call CalculateModifiedStat + inc c + ld a, c + cp NUM_STATS - 1 + jr nz, .loop + ret + +; calculate modified stat for stat c (0 = attack, 1 = defense, 2 = speed, 3 = special) +CalculateModifiedStat: + push bc + push bc + ld a, [wCalculateWhoseStats] + and a + ld a, c + ld hl, wBattleMonAttack + ld de, wPlayerMonUnmodifiedAttack + ld bc, wPlayerMonStatMods + jr z, .next + ld hl, wEnemyMonAttack + ld de, wEnemyMonUnmodifiedAttack + ld bc, wEnemyMonStatMods +.next + add c + ld c, a + jr nc, .noCarry1 + inc b +.noCarry1 + ld a, [bc] + pop bc + ld b, a + push bc + sla c + ld b, 0 + add hl, bc + ld a, c + add e + ld e, a + jr nc, .noCarry2 + inc d +.noCarry2 + pop bc + push hl + ld hl, StatModifierRatios + dec b + sla b + ld c, b + ld b, 0 + add hl, bc + xor a + ld [H_MULTIPLICAND], a + ld a, [de] + ld [H_MULTIPLICAND + 1], a + inc de + ld a, [de] + ld [H_MULTIPLICAND + 2], a + ld a, [hli] + ld [H_MULTIPLIER], a + call Multiply + ld a, [hl] + ld [H_DIVISOR], a + ld b, $4 + call Divide + pop hl + ld a, [H_DIVIDEND + 3] + sub 999 % $100 + ld a, [H_DIVIDEND + 2] + sbc 999 / $100 + jp c, .storeNewStatValue +; cap the stat at 999 + ld a, 999 / $100 + ld [H_DIVIDEND + 2], a + ld a, 999 % $100 + ld [H_DIVIDEND + 3], a +.storeNewStatValue + ld a, [H_DIVIDEND + 2] + ld [hli], a + ld b, a + ld a, [H_DIVIDEND + 3] + ld [hl], a + or b + jr nz, .done + inc [hl] ; if the stat is 0, bump it up to 1 +.done + pop bc + ret + +ApplyBadgeStatBoosts: + ld a, [wLinkState] + cp LINK_STATE_BATTLING + ret z ; return if link battle + ld a, [wObtainedBadges] + ld b, a + ld hl, wBattleMonAttack + ld c, $4 +; the boost is applied for badges whose bit position is even +; the order of boosts matches the order they are laid out in RAM +; Boulder (bit 0) - attack +; Thunder (bit 2) - defense +; Soul (bit 4) - speed +; Volcano (bit 6) - special +.loop + srl b + call c, .applyBoostToStat + inc hl + inc hl + srl b + dec c + jr nz, .loop + ret + +; multiply stat at hl by 1.125 +; cap stat at 999 +.applyBoostToStat + ld a, [hli] + ld d, a + ld e, [hl] + srl d + rr e + srl d + rr e + srl d + rr e + ld a, [hl] + add e + ld [hld], a + ld a, [hl] + adc d + ld [hli], a + ld a, [hld] + sub 999 % $100 + ld a, [hl] + sbc 999 / $100 + ret c + ld a, 999 / $100 + ld [hli], a + ld a, 999 % $100 + ld [hld], a + ret + +LoadHudAndHpBarAndStatusTilePatterns: + call LoadHpBarAndStatusTilePatterns + +LoadHudTilePatterns: + ld a, [rLCDC] + add a ; is LCD disabled? + jr c, .lcdEnabled +.lcdDisabled + ld hl, BattleHudTiles1 + ld de, vChars2 + $6d0 + ld bc, BattleHudTiles1End - BattleHudTiles1 + ld a, BANK(BattleHudTiles1) + call FarCopyDataDouble + ld hl, BattleHudTiles2 + ld de, vChars2 + $730 + ld bc, BattleHudTiles3End - BattleHudTiles2 + ld a, BANK(BattleHudTiles2) + jp FarCopyDataDouble +.lcdEnabled + ld de, BattleHudTiles1 + ld hl, vChars2 + $6d0 + lb bc, BANK(BattleHudTiles1), (BattleHudTiles1End - BattleHudTiles1) / $8 + call CopyVideoDataDouble + ld de, BattleHudTiles2 + ld hl, vChars2 + $730 + lb bc, BANK(BattleHudTiles2), (BattleHudTiles3End - BattleHudTiles2) / $8 + jp CopyVideoDataDouble + +PrintEmptyString: + ld hl, .emptyString + jp PrintText +.emptyString + db "@" + + +BattleRandom: +; Link battles use a shared PRNG. + + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jp nz, Random + + push hl + push bc + ld a, [wLinkBattleRandomNumberListIndex] + ld c, a + ld b, 0 + ld hl, wLinkBattleRandomNumberList + add hl, bc + inc a + ld [wLinkBattleRandomNumberListIndex], a + cp 9 + ld a, [hl] + pop bc + pop hl + ret c + +; if we picked the last seed, we need to recalculate the nine seeds + push hl + push bc + push af + +; point to seed 0 so we pick the first number the next time + xor a + ld [wLinkBattleRandomNumberListIndex], a + + ld hl, wLinkBattleRandomNumberList + ld b, 9 +.loop + ld a, [hl] + ld c, a +; multiply by 5 + add a + add a + add c +; add 1 + inc a + ld [hli], a + dec b + jr nz, .loop + + pop af + pop bc + pop hl + ret + + +HandleExplodingAnimation: + ld a, [H_WHOSETURN] + and a + ld hl, wEnemyMonType1 + ld de, wEnemyBattleStatus1 + ld a, [wPlayerMoveNum] + jr z, .player + ld hl, wBattleMonType1 + ld de, wEnemyBattleStatus1 + ld a, [wEnemyMoveNum] +.player + cp SELFDESTRUCT + jr z, .isExplodingMove + cp EXPLOSION + ret nz +.isExplodingMove + ld a, [de] + bit Invulnerable, a ; fly/dig + ret nz + ld a, [hli] + cp GHOST + ret z + ld a, [hl] + cp GHOST + ret z + ld a, [wMoveMissed] + and a + ret nz + ld a, 5 + ld [wAnimationType], a + +PlayMoveAnimation: + ld [wAnimationID],a + call Delay3 + predef_jump MoveAnimation + +InitBattle: + ld a, [wCurOpponent] + and a + jr z, DetermineWildOpponent + +InitOpponent: + ld a, [wCurOpponent] + ld [wcf91], a + ld [wEnemyMonSpecies2], a + jr InitBattleCommon + +DetermineWildOpponent: + ld a, [wd732] + bit 1, a + jr z, .asm_3ef2f + ld a, [hJoyHeld] + bit 1, a ; B button pressed? + ret nz +.asm_3ef2f + ld a, [wNumberOfNoRandomBattleStepsLeft] + and a + ret nz + callab TryDoWildEncounter + ret nz +InitBattleCommon: + ld a, [wMapPalOffset] + push af + ld hl, wLetterPrintingDelayFlags + ld a, [hl] + push af + res 1, [hl] + callab InitBattleVariables + ld a, [wEnemyMonSpecies2] + sub 200 + jp c, InitWildBattle + ld [wTrainerClass], a + call GetTrainerInformation + callab ReadTrainer + call DoBattleTransitionAndInitBattleVariables + call _LoadTrainerPic + xor a + ld [wEnemyMonSpecies2], a + ld [hStartTileID], a + dec a + ld [wAICount], a + coord hl, 12, 0 + predef CopyUncompressedPicToTilemap + ld a, $ff + ld [wEnemyMonPartyPos], a + ld a, $2 + ld [wIsInBattle], a + jp _InitBattleCommon + +InitWildBattle: + ld a, $1 + ld [wIsInBattle], a + call LoadEnemyMonData + call DoBattleTransitionAndInitBattleVariables + ld a, [wCurOpponent] + cp MAROWAK + jr z, .isGhost + call IsGhostBattle + jr nz, .isNoGhost +.isGhost + ld hl, wMonHSpriteDim + ld a, $66 + ld [hli], a ; write sprite dimensions + ld bc, GhostPic + ld a, c + ld [hli], a ; write front sprite pointer + ld [hl], b + ld hl, wEnemyMonNick ; set name to "GHOST" + ld a, "G" + ld [hli], a + ld a, "E" + ld [hli], a + ld a, "I" + ld [hli], a + ld a, "S" + ld [hli], a + ld a, "T" + ld [hli], a + ld [hl], "@" + ld a, [wcf91] + push af + ld a, MON_GHOST + ld [wcf91], a + ld de, vFrontPic + call LoadMonFrontSprite ; load ghost sprite + pop af + ld [wcf91], a + jr .spriteLoaded +.isNoGhost + ld de, vFrontPic + call LoadMonFrontSprite ; load mon sprite +.spriteLoaded + xor a + ld [wTrainerClass], a + ld [hStartTileID], a + coord hl, 12, 0 + predef CopyUncompressedPicToTilemap + +; common code that executes after init battle code specific to trainer or wild battles +_InitBattleCommon: + ld b, SET_PAL_BATTLE_BLACK + call RunPaletteCommand + call SlidePlayerAndEnemySilhouettesOnScreen + xor a + ld [H_AUTOBGTRANSFERENABLED], a + ld hl, .emptyString + call PrintText + call SaveScreenTilesToBuffer1 + call ClearScreen + ld a, $98 + ld [H_AUTOBGTRANSFERDEST + 1], a + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + call Delay3 + ld a, $9c + ld [H_AUTOBGTRANSFERDEST + 1], a + call LoadScreenTilesFromBuffer1 + coord hl, 9, 7 + lb bc, 5, 10 + call ClearScreenArea + coord hl, 1, 0 + lb bc, 4, 10 + call ClearScreenArea + call ClearSprites + ld a, [wIsInBattle] + dec a ; is it a wild battle? + call z, DrawEnemyHUDAndHPBar ; draw enemy HUD and HP bar if it's a wild battle + call StartBattle + callab EndOfBattle + pop af + ld [wLetterPrintingDelayFlags], a + pop af + ld [wMapPalOffset], a + ld a, [wSavedTilesetType] + ld [hTilesetType], a + scf + ret +.emptyString + db "@" + +_LoadTrainerPic: +; wd033-wd034 contain pointer to pic + ld a, [wTrainerPicPointer] + ld e, a + ld a, [wTrainerPicPointer + 1] + ld d, a ; de contains pointer to trainer pic + ld a, [wLinkState] + and a + ld a, Bank(TrainerPics) ; this is where all the trainer pics are (not counting Red's) + jr z, .loadSprite + ld a, Bank(RedPicFront) +.loadSprite + call UncompressSpriteFromDE + ld de, vFrontPic + ld a, $77 + ld c, a + jp LoadUncompressedSpriteData + +; unreferenced +ResetCryModifiers: + xor a + ld [wFrequencyModifier], a + ld [wTempoModifier], a + jp PlaySound + +; animates the mon "growing" out of the pokeball +AnimateSendingOutMon: + ld a, [wPredefRegisters] + ld h, a + ld a, [wPredefRegisters + 1] + ld l, a + ld a, [hStartTileID] + ld [hBaseTileID], a + ld b, $4c + ld a, [wIsInBattle] + and a + jr z, .notInBattle + add b + ld [hl], a + call Delay3 + ld bc, -(SCREEN_WIDTH * 2 + 1) + add hl, bc + ld a, 1 + ld [wDownscaledMonSize], a + lb bc, 3, 3 + predef CopyDownscaledMonTiles + ld c, 4 + call DelayFrames + ld bc, -(SCREEN_WIDTH * 2 + 1) + add hl, bc + xor a + ld [wDownscaledMonSize], a + lb bc, 5, 5 + predef CopyDownscaledMonTiles + ld c, 5 + call DelayFrames + ld bc, -(SCREEN_WIDTH * 2 + 1) + jr .next +.notInBattle + ld bc, -(SCREEN_WIDTH * 6 + 3) +.next + add hl, bc + ld a, [hBaseTileID] + add $31 + jr CopyUncompressedPicToHL + +CopyUncompressedPicToTilemap: + ld a, [wPredefRegisters] + ld h, a + ld a, [wPredefRegisters + 1] + ld l, a + ld a, [hStartTileID] +CopyUncompressedPicToHL: + lb bc, 7, 7 + ld de, SCREEN_WIDTH + push af + ld a, [wSpriteFlipped] + and a + jr nz, .flipped + pop af +.loop + push bc + push hl +.innerLoop + ld [hl], a + add hl, de + inc a + dec c + jr nz, .innerLoop + pop hl + inc hl + pop bc + dec b + jr nz, .loop + ret + +.flipped + push bc + ld b, 0 + dec c + add hl, bc + pop bc + pop af +.flippedLoop + push bc + push hl +.flippedInnerLoop + ld [hl], a + add hl, de + inc a + dec c + jr nz, .flippedInnerLoop + pop hl + dec hl + pop bc + dec b + jr nz, .flippedLoop + ret + +LoadMonBackPic: +; Assumes the monster's attributes have +; been loaded with GetMonHeader. + ld a, [wBattleMonSpecies2] + ld [wcf91], a + coord hl, 1, 5 + ld b, 7 + ld c, 8 + call ClearScreenArea + ld hl, wMonHBackSprite - wMonHeader + call UncompressMonSprite + predef ScaleSpriteByTwo + ld de, vBackPic + call InterlaceMergeSpriteBuffers ; combine the two buffers to a single 2bpp sprite + ld hl, vSprites + ld de, vBackPic + ld c, (2*SPRITEBUFFERSIZE)/16 ; count of 16-byte chunks to be copied + ld a, [H_LOADEDROMBANK] + ld b, a + jp CopyVideoData + +JumpMoveEffect: + call _JumpMoveEffect + ld b, $1 + ret + +_JumpMoveEffect: + ld a, [H_WHOSETURN] + and a + ld a, [wPlayerMoveEffect] + jr z, .next1 + ld a, [wEnemyMoveEffect] +.next1 + dec a ; subtract 1, there is no special effect for 00 + add a ; x2, 16bit pointers + ld hl, MoveEffectPointerTable + ld b, 0 + ld c, a + add hl, bc + ld a, [hli] + ld h, [hl] + ld l, a + jp hl ; jump to special effect handler + +MoveEffectPointerTable: + dw SleepEffect ; unused effect + dw PoisonEffect ; POISON_SIDE_EFFECT1 + dw DrainHPEffect ; DRAIN_HP_EFFECT + dw FreezeBurnParalyzeEffect ; BURN_SIDE_EFFECT1 + dw FreezeBurnParalyzeEffect ; FREEZE_SIDE_EFFECT + dw FreezeBurnParalyzeEffect ; PARALYZE_SIDE_EFFECT1 + dw ExplodeEffect ; EXPLODE_EFFECT + dw DrainHPEffect ; DREAM_EATER_EFFECT + dw $0000 ; MIRROR_MOVE_EFFECT + dw StatModifierUpEffect ; ATTACK_UP1_EFFECT + dw StatModifierUpEffect ; DEFENSE_UP1_EFFECT + dw StatModifierUpEffect ; SPEED_UP1_EFFECT + dw StatModifierUpEffect ; SPECIAL_UP1_EFFECT + dw StatModifierUpEffect ; ACCURACY_UP1_EFFECT + dw StatModifierUpEffect ; EVASION_UP1_EFFECT + dw PayDayEffect ; PAY_DAY_EFFECT + dw $0000 ; SWIFT_EFFECT + dw StatModifierDownEffect ; ATTACK_DOWN1_EFFECT + dw StatModifierDownEffect ; DEFENSE_DOWN1_EFFECT + dw StatModifierDownEffect ; SPEED_DOWN1_EFFECT + dw StatModifierDownEffect ; SPECIAL_DOWN1_EFFECT + dw StatModifierDownEffect ; ACCURACY_DOWN1_EFFECT + dw StatModifierDownEffect ; EVASION_DOWN1_EFFECT + dw ConversionEffect ; CONVERSION_EFFECT + dw HazeEffect ; HAZE_EFFECT + dw BideEffect ; BIDE_EFFECT + dw ThrashPetalDanceEffect ; THRASH_PETAL_DANCE_EFFECT + dw SwitchAndTeleportEffect ; SWITCH_AND_TELEPORT_EFFECT + dw TwoToFiveAttacksEffect ; TWO_TO_FIVE_ATTACKS_EFFECT + dw TwoToFiveAttacksEffect ; unused effect + dw FlinchSideEffect ; FLINCH_SIDE_EFFECT1 + dw SleepEffect ; SLEEP_EFFECT + dw PoisonEffect ; POISON_SIDE_EFFECT2 + dw FreezeBurnParalyzeEffect ; BURN_SIDE_EFFECT2 + dw FreezeBurnParalyzeEffect ; unused effect + dw FreezeBurnParalyzeEffect ; PARALYZE_SIDE_EFFECT2 + dw FlinchSideEffect ; FLINCH_SIDE_EFFECT2 + dw OneHitKOEffect ; OHKO_EFFECT + dw ChargeEffect ; CHARGE_EFFECT + dw $0000 ; SUPER_FANG_EFFECT + dw $0000 ; SPECIAL_DAMAGE_EFFECT + dw TrappingEffect ; TRAPPING_EFFECT + dw ChargeEffect ; FLY_EFFECT + dw TwoToFiveAttacksEffect ; ATTACK_TWICE_EFFECT + dw $0000 ; JUMP_KICK_EFFECT + dw MistEffect ; MIST_EFFECT + dw FocusEnergyEffect ; FOCUS_ENERGY_EFFECT + dw RecoilEffect ; RECOIL_EFFECT + dw ConfusionEffect ; CONFUSION_EFFECT + dw StatModifierUpEffect ; ATTACK_UP2_EFFECT + dw StatModifierUpEffect ; DEFENSE_UP2_EFFECT + dw StatModifierUpEffect ; SPEED_UP2_EFFECT + dw StatModifierUpEffect ; SPECIAL_UP2_EFFECT + dw StatModifierUpEffect ; ACCURACY_UP2_EFFECT + dw StatModifierUpEffect ; EVASION_UP2_EFFECT + dw HealEffect ; HEAL_EFFECT + dw TransformEffect ; TRANSFORM_EFFECT + dw StatModifierDownEffect ; ATTACK_DOWN2_EFFECT + dw StatModifierDownEffect ; DEFENSE_DOWN2_EFFECT + dw StatModifierDownEffect ; SPEED_DOWN2_EFFECT + dw StatModifierDownEffect ; SPECIAL_DOWN2_EFFECT + dw StatModifierDownEffect ; ACCURACY_DOWN2_EFFECT + dw StatModifierDownEffect ; EVASION_DOWN2_EFFECT + dw ReflectLightScreenEffect ; LIGHT_SCREEN_EFFECT + dw ReflectLightScreenEffect ; REFLECT_EFFECT + dw PoisonEffect ; POISON_EFFECT + dw ParalyzeEffect ; PARALYZE_EFFECT + dw StatModifierDownEffect ; ATTACK_DOWN_SIDE_EFFECT + dw StatModifierDownEffect ; DEFENSE_DOWN_SIDE_EFFECT + dw StatModifierDownEffect ; SPEED_DOWN_SIDE_EFFECT + dw StatModifierDownEffect ; SPECIAL_DOWN_SIDE_EFFECT + dw StatModifierDownEffect ; unused effect + dw StatModifierDownEffect ; unused effect + dw StatModifierDownEffect ; unused effect + dw StatModifierDownEffect ; unused effect + dw ConfusionSideEffect ; CONFUSION_SIDE_EFFECT + dw TwoToFiveAttacksEffect ; TWINEEDLE_EFFECT + dw $0000 ; unused effect + dw SubstituteEffect ; SUBSTITUTE_EFFECT + dw HyperBeamEffect ; HYPER_BEAM_EFFECT + dw RageEffect ; RAGE_EFFECT + dw MimicEffect ; MIMIC_EFFECT + dw $0000 ; METRONOME_EFFECT + dw LeechSeedEffect ; LEECH_SEED_EFFECT + dw SplashEffect ; SPLASH_EFFECT + dw DisableEffect ; DISABLE_EFFECT + +SleepEffect: + ld de, wEnemyMonStatus + ld bc, wEnemyBattleStatus2 + ld a, [H_WHOSETURN] + and a + jp z, .sleepEffect + ld de, wBattleMonStatus + ld bc, wPlayerBattleStatus2 + +.sleepEffect + ld a, [bc] + bit NeedsToRecharge, a ; does the target need to recharge? (hyper beam) + res NeedsToRecharge, a ; target no longer needs to recharge + ld [bc], a + jr nz, .setSleepCounter ; if the target had to recharge, all hit tests will be skipped + ; including the event where the target already has another status + ld a, [de] + ld b, a + and $7 + jr z, .notAlreadySleeping ; can't affect a mon that is already asleep + ld hl, AlreadyAsleepText + jp PrintText +.notAlreadySleeping + ld a, b + and a + jr nz, .didntAffect ; can't affect a mon that is already statused + push de + call MoveHitTest ; apply accuracy tests + pop de + ld a, [wMoveMissed] + and a + jr nz, .didntAffect +.setSleepCounter +; set target's sleep counter to a random number between 1 and 7 + call BattleRandom + and $7 + jr z, .setSleepCounter + ld [de], a + call PlayCurrentMoveAnimation2 + ld hl, FellAsleepText + jp PrintText +.didntAffect + jp PrintDidntAffectText + +FellAsleepText: + TX_FAR _FellAsleepText + db "@" + +AlreadyAsleepText: + TX_FAR _AlreadyAsleepText + db "@" + +PoisonEffect: + ld hl, wEnemyMonStatus + ld de, wPlayerMoveEffect + ld a, [H_WHOSETURN] + and a + jr z, .poisonEffect + ld hl, wBattleMonStatus + ld de, wEnemyMoveEffect +.poisonEffect + call CheckTargetSubstitute + jr nz, .noEffect ; can't poison a substitute target + ld a, [hli] + ld b, a + and a + jr nz, .noEffect ; miss if target is already statused + ld a, [hli] + cp POISON ; can't poison a poison-type target + jr z, .noEffect + ld a, [hld] + cp POISON ; can't poison a poison-type target + jr z, .noEffect + ld a, [de] + cp POISON_SIDE_EFFECT1 + ld b, $34 ; ~20% chance of poisoning + jr z, .sideEffectTest + cp POISON_SIDE_EFFECT2 + ld b, $67 ; ~40% chance of poisoning + jr z, .sideEffectTest + push hl + push de + call MoveHitTest ; apply accuracy tests + pop de + pop hl + ld a, [wMoveMissed] + and a + jr nz, .didntAffect + jr .inflictPoison +.sideEffectTest + call BattleRandom + cp b ; was side effect successful? + ret nc +.inflictPoison + dec hl + set 3, [hl] ; mon is now poisoned + push de + dec de + ld a, [H_WHOSETURN] + and a + ld b, ANIM_C7 + ld hl, wPlayerBattleStatus3 + ld a, [de] + ld de, wPlayerToxicCounter + jr nz, .ok + ld b, ANIM_A9 + ld hl, wEnemyBattleStatus3 + ld de, wEnemyToxicCounter +.ok + cp TOXIC + jr nz, .normalPoison ; done if move is not Toxic + set BadlyPoisoned, [hl] ; else set Toxic battstatus + xor a + ld [de], a + ld hl, BadlyPoisonedText + jr .continue +.normalPoison + ld hl, PoisonedText +.continue + pop de + ld a, [de] + cp POISON_EFFECT + jr z, .regularPoisonEffect + ld a, b + call PlayBattleAnimation2 + jp PrintText +.regularPoisonEffect + call PlayCurrentMoveAnimation2 + jp PrintText +.noEffect + ld a, [de] + cp POISON_EFFECT + ret nz +.didntAffect + ld c, 50 + call DelayFrames + jp PrintDidntAffectText + +PoisonedText: + TX_FAR _PoisonedText + db "@" + +BadlyPoisonedText: + TX_FAR _BadlyPoisonedText + db "@" + +DrainHPEffect: + jpab DrainHPEffect_ + +ExplodeEffect: + ld hl, wBattleMonHP + ld de, wPlayerBattleStatus2 + ld a, [H_WHOSETURN] + and a + jr z, .faintUser + ld hl, wEnemyMonHP + ld de, wEnemyBattleStatus2 +.faintUser + xor a + ld [hli], a ; set the mon's HP to 0 + ld [hli], a + inc hl + ld [hl], a ; set mon's status to 0 + ld a, [de] + res Seeded, a ; clear mon's leech seed status + ld [de], a + ret + +FreezeBurnParalyzeEffect: + xor a + ld [wAnimationType], a + call CheckTargetSubstitute ; test bit 4 of d063/d068 flags [target has substitute flag] + ret nz ; return if they have a substitute, can't effect them + ld a, [H_WHOSETURN] + and a + jp nz, opponentAttacker + ld a, [wEnemyMonStatus] + and a + jp nz, CheckDefrost ; can't inflict status if opponent is already statused + ld a, [wPlayerMoveType] + ld b, a + ld a, [wEnemyMonType1] + cp b ; do target type 1 and move type match? + ret z ; return if they match (an ice move can't freeze an ice-type, body slam can't paralyze a normal-type, etc.) + ld a, [wEnemyMonType2] + cp b ; do target type 2 and move type match? + ret z ; return if they match + ld a, [wPlayerMoveEffect] + cp a, PARALYZE_SIDE_EFFECT1 + 1 ; 10% status effects are 04, 05, 06 so 07 will set carry for those + ld b, $1a ; 0x1A/0x100 or 26/256 = 10.2%~ chance + jr c, .next1 ; branch ahead if this is a 10% chance effect.. + ld b, $4d ; else use 0x4D/0x100 or 77/256 = 30.1%~ chance + sub a, $1e ; subtract $1E to map to equivalent 10% chance effects +.next1 + push af + call BattleRandom ; get random 8bit value for probability test + cp b + pop bc + ret nc ; do nothing if random value is >= 1A or 4D [no status applied] + ld a, b ; what type of effect is this? + cp a, BURN_SIDE_EFFECT1 + jr z, .burn + cp a, FREEZE_SIDE_EFFECT + jr z, .freeze +; .paralyze + ld a, 1 << PAR + ld [wEnemyMonStatus], a + call QuarterSpeedDueToParalysis ; quarter speed of affected mon + ld a, ANIM_A9 + call PlayBattleAnimation + jp PrintMayNotAttackText ; print paralysis text +.burn + ld a, 1 << BRN + ld [wEnemyMonStatus], a + call HalveAttackDueToBurn ; halve attack of affected mon + ld a, ANIM_A9 + call PlayBattleAnimation + ld hl, BurnedText + jp PrintText +.freeze + call ClearHyperBeam ; resets hyper beam (recharge) condition from target + ld a, 1 << FRZ + ld [wEnemyMonStatus], a + ld a, ANIM_A9 + call PlayBattleAnimation + ld hl, FrozenText + jp PrintText +opponentAttacker: + ld a, [wBattleMonStatus] ; mostly same as above with addresses swapped for opponent + and a + jp nz, CheckDefrost + ld a, [wEnemyMoveType] + ld b, a + ld a, [wBattleMonType1] + cp b + ret z + ld a, [wBattleMonType2] + cp b + ret z + ld a, [wEnemyMoveEffect] + cp a, PARALYZE_SIDE_EFFECT1 + 1 + ld b, $1a + jr c, .next1 + ld b, $4d + sub a, $1e +.next1 + push af + call BattleRandom + cp b + pop bc + ret nc + ld a, b + cp a, BURN_SIDE_EFFECT1 + jr z, .burn + cp a, FREEZE_SIDE_EFFECT + jr z, .freeze + ld a, 1 << PAR + ld [wBattleMonStatus], a + call QuarterSpeedDueToParalysis + jp PrintMayNotAttackText +.burn + ld a, 1 << BRN + ld [wBattleMonStatus], a + call HalveAttackDueToBurn + ld hl, BurnedText + jp PrintText +.freeze +; hyper beam bits aren't reseted for opponent's side + ld a, 1 << FRZ + ld [wBattleMonStatus], a + ld hl, FrozenText + jp PrintText + +BurnedText: + TX_FAR _BurnedText + db "@" + +FrozenText: + TX_FAR _FrozenText + db "@" + +CheckDefrost: +; any fire-type move that has a chance inflict burn (all but Fire Spin) will defrost a frozen target + and a, 1 << FRZ ; are they frozen? + ret z ; return if so + ld a, [H_WHOSETURN] + and a + jr nz, .opponent + ;player [attacker] + ld a, [wPlayerMoveType] + sub a, FIRE + ret nz ; return if type of move used isn't fire + ld [wEnemyMonStatus], a ; set opponent status to 00 ["defrost" a frozen monster] + ld hl, wEnemyMon1Status + ld a, [wEnemyMonPartyPos] + ld bc, wEnemyMon2 - wEnemyMon1 + call AddNTimes + xor a + ld [hl], a ; clear status in roster + ld hl, FireDefrostedText + jr .common +.opponent + ld a, [wEnemyMoveType] ; same as above with addresses swapped + sub a, FIRE + ret nz + ld [wBattleMonStatus], a + ld hl, wPartyMon1Status + ld a, [wPlayerMonNumber] + ld bc, wPartyMon2 - wPartyMon1 + call AddNTimes + xor a + ld [hl], a + ld hl, FireDefrostedText +.common + jp PrintText + +FireDefrostedText: + TX_FAR _FireDefrostedText + db "@" + +StatModifierUpEffect: + ld hl, wPlayerMonStatMods + ld de, wPlayerMoveEffect + ld a, [H_WHOSETURN] + and a + jr z, .statModifierUpEffect + ld hl, wEnemyMonStatMods + ld de, wEnemyMoveEffect +.statModifierUpEffect + ld a, [de] + sub ATTACK_UP1_EFFECT + cp EVASION_UP1_EFFECT + $3 - ATTACK_UP1_EFFECT ; covers all +1 effects + jr c, .incrementStatMod + sub ATTACK_UP2_EFFECT - ATTACK_UP1_EFFECT ; map +2 effects to equivalent +1 effect +.incrementStatMod + ld c, a + ld b, $0 + add hl, bc + ld b, [hl] + inc b ; increment corresponding stat mod + ld a, $d + cp b ; can't raise stat past +6 ($d or 13) + jp c, PrintNothingHappenedText + ld a, [de] + cp ATTACK_UP1_EFFECT + $8 ; is it a +2 effect? + jr c, .ok + inc b ; if so, increment stat mod again + ld a, $d + cp b ; unless it's already +6 + jr nc, .ok + ld b, a +.ok + ld [hl], b + ld a, c + cp $4 + jr nc, UpdateStatDone ; jump if mod affected is evasion/accuracy + push hl + ld hl, wBattleMonAttack + 1 + ld de, wPlayerMonUnmodifiedAttack + ld a, [H_WHOSETURN] + and a + jr z, .pointToStats + ld hl, wEnemyMonAttack + 1 + ld de, wEnemyMonUnmodifiedAttack +.pointToStats + push bc + sla c + ld b, $0 + add hl, bc ; hl = modified stat + ld a, c + add e + ld e, a + jr nc, .checkIf999 + inc d ; de = unmodified (original) stat +.checkIf999 + pop bc + ld a, [hld] + sub 999 % $100 ; check if stat is already 999 + jr nz, .recalculateStat + ld a, [hl] + sbc 999 / $100 + jp z, RestoreOriginalStatModifier +.recalculateStat ; recalculate affected stat + ; paralysis and burn penalties, as well as badge boosts are ignored + push hl + push bc + ld hl, StatModifierRatios + dec b + sla b + ld c, b + ld b, $0 + add hl, bc + pop bc + xor a + ld [H_MULTIPLICAND], a + ld a, [de] + ld [H_MULTIPLICAND + 1], a + inc de + ld a, [de] + ld [H_MULTIPLICAND + 2], a + ld a, [hli] + ld [H_MULTIPLIER], a + call Multiply + ld a, [hl] + ld [H_DIVISOR], a + ld b, $4 + call Divide + pop hl +; cap at 999 + ld a, [H_PRODUCT + 3] + sub 999 % $100 + ld a, [H_PRODUCT + 2] + sbc 999 / $100 + jp c, UpdateStat + ld a, 999 / $100 + ld [H_MULTIPLICAND + 1], a + ld a, 999 % $100 + ld [H_MULTIPLICAND + 2], a + +UpdateStat: + ld a, [H_PRODUCT + 2] + ld [hli], a + ld a, [H_PRODUCT + 3] + ld [hl], a + pop hl +UpdateStatDone: + ld b, c + inc b + call PrintStatText + ld hl, wPlayerBattleStatus2 + ld de, wPlayerMoveNum + ld bc, wPlayerMonMinimized + ld a, [H_WHOSETURN] + and a + jr z, .asm_3f4e6 + ld hl, wEnemyBattleStatus2 + ld de, wEnemyMoveNum + ld bc, wEnemyMonMinimized +.asm_3f4e6 + ld a, [de] + cp MINIMIZE + jr nz, .asm_3f4f9 + ; if a substitute is up, slide off the substitute and show the mon pic before + ; playing the minimize animation + bit HasSubstituteUp, [hl] + push af + push bc + ld hl, HideSubstituteShowMonAnim + ld b, BANK(HideSubstituteShowMonAnim) + push de + call nz, Bankswitch + pop de +.asm_3f4f9 + call PlayCurrentMoveAnimation + ld a, [de] + cp MINIMIZE + jr nz, .applyBadgeBoostsAndStatusPenalties + pop bc + ld a, $1 + ld [bc], a + ld hl, ReshowSubstituteAnim + ld b, BANK(ReshowSubstituteAnim) + pop af + call nz, Bankswitch +.applyBadgeBoostsAndStatusPenalties + ld a, [H_WHOSETURN] + and a + call z, ApplyBadgeStatBoosts ; whenever the player uses a stat-up move, badge boosts get reapplied again to every stat, + ; even to those not affected by the stat-up move (will be boosted further) + ld hl, MonsStatsRoseText + call PrintText + +; these shouldn't be here + call QuarterSpeedDueToParalysis ; apply speed penalty to the player whose turn is not, if it's paralyzed + jp HalveAttackDueToBurn ; apply attack penalty to the player whose turn is not, if it's burned + +RestoreOriginalStatModifier: + pop hl + dec [hl] + +PrintNothingHappenedText: + ld hl, NothingHappenedText + jp PrintText + +MonsStatsRoseText: + TX_FAR _MonsStatsRoseText + TX_ASM + ld hl, GreatlyRoseText + ld a, [H_WHOSETURN] + and a + ld a, [wPlayerMoveEffect] + jr z, .playerTurn + ld a, [wEnemyMoveEffect] +.playerTurn + cp ATTACK_DOWN1_EFFECT + ret nc + ld hl, RoseText + ret + +GreatlyRoseText: + TX_DELAY + TX_FAR _GreatlyRoseText +; fallthrough +RoseText: + TX_FAR _RoseText + db "@" + +StatModifierDownEffect: + ld hl, wEnemyMonStatMods + ld de, wPlayerMoveEffect + ld bc, wEnemyBattleStatus1 + ld a, [H_WHOSETURN] + and a + jr z, .statModifierDownEffect + ld hl, wPlayerMonStatMods + ld de, wEnemyMoveEffect + ld bc, wPlayerBattleStatus1 + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr z, .statModifierDownEffect + call BattleRandom + cp $40 ; 1/4 chance to miss by in regular battle + jp c, MoveMissed +.statModifierDownEffect + call CheckTargetSubstitute ; can't hit through substitute + jp nz, MoveMissed + ld a, [de] + cp ATTACK_DOWN_SIDE_EFFECT + jr c, .nonSideEffect + call BattleRandom + cp $55 ; 85/256 chance for side effects + jp nc, CantLowerAnymore + ld a, [de] + sub ATTACK_DOWN_SIDE_EFFECT ; map each stat to 0-3 + jr .decrementStatMod +.nonSideEffect ; non-side effects only + push hl + push de + push bc + call MoveHitTest ; apply accuracy tests + pop bc + pop de + pop hl + ld a, [wMoveMissed] + and a + jp nz, MoveMissed + ld a, [bc] + bit Invulnerable, a ; fly/dig + jp nz, MoveMissed + ld a, [de] + sub ATTACK_DOWN1_EFFECT + cp EVASION_DOWN1_EFFECT + $3 - ATTACK_DOWN1_EFFECT ; covers all -1 effects + jr c, .decrementStatMod + sub ATTACK_DOWN2_EFFECT - ATTACK_DOWN1_EFFECT ; map -2 effects to corresponding -1 effect +.decrementStatMod + ld c, a + ld b, $0 + add hl, bc + ld b, [hl] + dec b ; dec corresponding stat mod + jp z, CantLowerAnymore ; if stat mod is 1 (-6), can't lower anymore + ld a, [de] + cp ATTACK_DOWN2_EFFECT - $16 ; $24 + jr c, .ok + cp EVASION_DOWN2_EFFECT + $5 ; $44 + jr nc, .ok + dec b ; stat down 2 effects only (dec mod again) + jr nz, .ok + inc b ; increment mod to 1 (-6) if it would become 0 (-7) +.ok + ld [hl], b ; save modified mod + ld a, c + cp $4 + jr nc, UpdateLoweredStatDone ; jump for evasion/accuracy + push hl + push de + ld hl, wEnemyMonAttack + 1 + ld de, wEnemyMonUnmodifiedAttack + ld a, [H_WHOSETURN] + and a + jr z, .pointToStat + ld hl, wBattleMonAttack + 1 + ld de, wPlayerMonUnmodifiedAttack +.pointToStat + push bc + sla c + ld b, $0 + add hl, bc ; hl = modified stat + ld a, c + add e + ld e, a + jr nc, .noCarry + inc d ; de = unmodified stat +.noCarry + pop bc + ld a, [hld] + sub $1 ; can't lower stat below 1 (-6) + jr nz, .recalculateStat + ld a, [hl] + and a + jp z, CantLowerAnymore_Pop +.recalculateStat +; recalculate affected stat +; paralysis and burn penalties, as well as badge boosts are ignored + push hl + push bc + ld hl, StatModifierRatios + dec b + sla b + ld c, b + ld b, $0 + add hl, bc + pop bc + xor a + ld [H_MULTIPLICAND], a + ld a, [de] + ld [H_MULTIPLICAND + 1], a + inc de + ld a, [de] + ld [H_MULTIPLICAND + 2], a + ld a, [hli] + ld [H_MULTIPLIER], a + call Multiply + ld a, [hl] + ld [H_DIVISOR], a + ld b, $4 + call Divide + pop hl + ld a, [H_PRODUCT + 3] + ld b, a + ld a, [H_PRODUCT + 2] + or b + jp nz, UpdateLoweredStat + ld [H_MULTIPLICAND + 1], a + ld a, $1 + ld [H_MULTIPLICAND + 2], a + +UpdateLoweredStat: + ld a, [H_PRODUCT + 2] + ld [hli], a + ld a, [H_PRODUCT + 3] + ld [hl], a + pop de + pop hl +UpdateLoweredStatDone: + ld b, c + inc b + push de + call PrintStatText + pop de + ld a, [de] + cp $44 + jr nc, .ApplyBadgeBoostsAndStatusPenalties + call PlayCurrentMoveAnimation2 +.ApplyBadgeBoostsAndStatusPenalties + ld a, [H_WHOSETURN] + and a + call nz, ApplyBadgeStatBoosts ; whenever the player uses a stat-down move, badge boosts get reapplied again to every stat, + ; even to those not affected by the stat-up move (will be boosted further) + ld hl, MonsStatsFellText + call PrintText + +; These where probably added given that a stat-down move affecting speed or attack will override +; the stat penalties from paralysis and burn respectively. +; But they are always called regardless of the stat affected by the stat-down move. + call QuarterSpeedDueToParalysis + jp HalveAttackDueToBurn + +CantLowerAnymore_Pop: + pop de + pop hl + inc [hl] + +CantLowerAnymore: + ld a, [de] + cp ATTACK_DOWN_SIDE_EFFECT + ret nc + ld hl, NothingHappenedText + jp PrintText + +MoveMissed: + ld a, [de] + cp $44 + ret nc + jp ConditionalPrintButItFailed + +MonsStatsFellText: + TX_FAR _MonsStatsFellText + TX_ASM + ld hl, FellText + ld a, [H_WHOSETURN] + and a + ld a, [wPlayerMoveEffect] + jr z, .playerTurn + ld a, [wEnemyMoveEffect] +.playerTurn +; check if the move's effect decreases a stat by 2 + cp BIDE_EFFECT + ret c + cp ATTACK_DOWN_SIDE_EFFECT + ret nc + ld hl, GreatlyFellText + ret + +GreatlyFellText: + TX_DELAY + TX_FAR _GreatlyFellText +; fallthrough +FellText: + TX_FAR _FellText + db "@" + +PrintStatText: + ld hl, StatsTextStrings + ld c, "@" +.findStatName_outer + dec b + jr z, .foundStatName +.findStatName_inner + ld a, [hli] + cp c + jr z, .findStatName_outer + jr .findStatName_inner +.foundStatName + ld de, wcf50 + ld bc, $a + jp CopyData + +StatsTextStrings: + db "ANGR@" + db "VERT@" + db "INIT@" + db "SPEZ@" + db "GENA@" + db "FLU@" + +StatModifierRatios: +; first byte is numerator, second byte is denominator + db 25, 100 ; 0.25 + db 28, 100 ; 0.28 + db 33, 100 ; 0.33 + db 40, 100 ; 0.40 + db 50, 100 ; 0.50 + db 66, 100 ; 0.66 + db 1, 1 ; 1.00 + db 15, 10 ; 1.50 + db 2, 1 ; 2.00 + db 25, 10 ; 2.50 + db 3, 1 ; 3.00 + db 35, 10 ; 3.50 + db 4, 1 ; 4.00 + +BideEffect: + ld hl, wPlayerBattleStatus1 + ld de, wPlayerBideAccumulatedDamage + ld bc, wPlayerNumAttacksLeft + ld a, [H_WHOSETURN] + and a + jr z, .bideEffect + ld hl, wEnemyBattleStatus1 + ld de, wEnemyBideAccumulatedDamage + ld bc, wEnemyNumAttacksLeft +.bideEffect + set StoringEnergy, [hl] ; mon is now using bide + xor a + ld [de], a + inc de + ld [de], a + ld [wPlayerMoveEffect], a + ld [wEnemyMoveEffect], a + call BattleRandom + and $1 + inc a + inc a + ld [bc], a ; set Bide counter to 2 or 3 at random + ld a, [H_WHOSETURN] + add XSTATITEM_ANIM + jp PlayBattleAnimation2 + +ThrashPetalDanceEffect: + ld hl, wPlayerBattleStatus1 + ld de, wPlayerNumAttacksLeft + ld a, [H_WHOSETURN] + and a + jr z, .thrashPetalDanceEffect + ld hl, wEnemyBattleStatus1 + ld de, wEnemyNumAttacksLeft +.thrashPetalDanceEffect + set ThrashingAbout, [hl] ; mon is now using thrash/petal dance + call BattleRandom + and $1 + inc a + inc a + ld [de], a ; set thrash/petal dance counter to 2 or 3 at random + ld a, [H_WHOSETURN] + add ANIM_B0 + jp PlayBattleAnimation2 + +SwitchAndTeleportEffect: + ld a, [H_WHOSETURN] + and a + jr nz, .handleEnemy + ld a, [wIsInBattle] + dec a + jr nz, .notWildBattle1 + ld a, [wCurEnemyLVL] + ld b, a + ld a, [wBattleMonLevel] + cp b ; is the player's level greater than the enemy's level? + jr nc, .playerMoveWasSuccessful ; if so, teleport will always succeed + add b + ld c, a + inc c ; c = sum of player level and enemy level +.rejectionSampleLoop1 + call BattleRandom + cp c ; get a random number between 0 and c + jr nc, .rejectionSampleLoop1 + srl b + srl b ; b = enemyLevel / 4 + cp b ; is rand[0, playerLevel + enemyLevel) >= (enemyLevel / 4)? + jr nc, .playerMoveWasSuccessful ; if so, allow teleporting + ld c, 50 + call DelayFrames + ld a, [wPlayerMoveNum] + cp TELEPORT + jp nz, PrintDidntAffectText + jp PrintButItFailedText_ +.playerMoveWasSuccessful + call ReadPlayerMonCurHPAndStatus + xor a + ld [wAnimationType], a + inc a + ld [wEscapedFromBattle], a + ld a, [wPlayerMoveNum] + jr .playAnimAndPrintText +.notWildBattle1 + ld c, 50 + call DelayFrames + ld hl, IsUnaffectedText + ld a, [wPlayerMoveNum] + cp TELEPORT + jp nz, PrintText + jp PrintButItFailedText_ +.handleEnemy + ld a, [wIsInBattle] + dec a + jr nz, .notWildBattle2 + ld a, [wBattleMonLevel] + ld b, a + ld a, [wCurEnemyLVL] + cp b + jr nc, .enemyMoveWasSuccessful + add b + ld c, a + inc c +.rejectionSampleLoop2 + call BattleRandom + cp c + jr nc, .rejectionSampleLoop2 + srl b + srl b + cp b + jr nc, .enemyMoveWasSuccessful + ld c, 50 + call DelayFrames + ld a, [wEnemyMoveNum] + cp TELEPORT + jp nz, PrintDidntAffectText + jp PrintButItFailedText_ +.enemyMoveWasSuccessful + call ReadPlayerMonCurHPAndStatus + xor a + ld [wAnimationType], a + inc a + ld [wEscapedFromBattle], a + ld a, [wEnemyMoveNum] + jr .playAnimAndPrintText +.notWildBattle2 + ld c, 50 + call DelayFrames + ld hl, IsUnaffectedText + ld a, [wEnemyMoveNum] + cp TELEPORT + jp nz, PrintText + jp ConditionalPrintButItFailed +.playAnimAndPrintText + push af + call PlayBattleAnimation + ld c, 20 + call DelayFrames + pop af + ld hl, RanFromBattleText + cp TELEPORT + jr z, .printText + ld hl, RanAwayScaredText + cp ROAR + jr z, .printText + ld hl, WasBlownAwayText +.printText + jp PrintText + +RanFromBattleText: + TX_FAR _RanFromBattleText + db "@" + +RanAwayScaredText: + TX_FAR _RanAwayScaredText + db "@" + +WasBlownAwayText: + TX_FAR _WasBlownAwayText + db "@" + +TwoToFiveAttacksEffect: + ld hl, wPlayerBattleStatus1 + ld de, wPlayerNumAttacksLeft + ld bc, wPlayerNumHits + ld a, [H_WHOSETURN] + and a + jr z, .twoToFiveAttacksEffect + ld hl, wEnemyBattleStatus1 + ld de, wEnemyNumAttacksLeft + ld bc, wEnemyNumHits +.twoToFiveAttacksEffect + bit AttackingMultipleTimes, [hl] ; is mon attacking multiple times? + ret nz + set AttackingMultipleTimes, [hl] ; mon is now attacking multiple times + ld hl, wPlayerMoveEffect + ld a, [H_WHOSETURN] + and a + jr z, .setNumberOfHits + ld hl, wEnemyMoveEffect +.setNumberOfHits + ld a, [hl] + cp TWINEEDLE_EFFECT + jr z, .twineedle + cp ATTACK_TWICE_EFFECT + ld a, $2 ; number of hits it's always 2 for ATTACK_TWICE_EFFECT + jr z, .saveNumberOfHits +; for TWO_TO_FIVE_ATTACKS_EFFECT 3/8 chance for 2 and 3 hits, and 1/8 chance for 4 and 5 hits + call BattleRandom + and $3 + cp $2 + jr c, .gotNumHits +; if the number of hits was greater than 2, re-roll again for a lower chance + call BattleRandom + and $3 +.gotNumHits + inc a + inc a +.saveNumberOfHits + ld [de], a + ld [bc], a + ret +.twineedle + ld a, POISON_SIDE_EFFECT1 + ld [hl], a ; set Twineedle's effect to poison effect + jr .saveNumberOfHits + +FlinchSideEffect: + call CheckTargetSubstitute + ret nz + ld hl, wEnemyBattleStatus1 + ld de, wPlayerMoveEffect + ld a, [H_WHOSETURN] + and a + jr z, .flinchSideEffect + ld hl, wPlayerBattleStatus1 + ld de, wEnemyMoveEffect +.flinchSideEffect + ld a, [de] + cp FLINCH_SIDE_EFFECT1 + ld b, $1a ; ~10% chance of flinch + jr z, .gotEffectChance + ld b, $4d ; ~30% chance of flinch +.gotEffectChance + call BattleRandom + cp b + ret nc + set Flinched, [hl] ; set mon's status to flinching + call ClearHyperBeam + ret + +OneHitKOEffect: + jpab OneHitKOEffect_ + +ChargeEffect: + ld hl, wPlayerBattleStatus1 + ld de, wPlayerMoveEffect + ld a, [H_WHOSETURN] + and a + ld b, XSTATITEM_ANIM + jr z, .chargeEffect + ld hl, wEnemyBattleStatus1 + ld de, wEnemyMoveEffect + ld b, ANIM_AF +.chargeEffect + set ChargingUp, [hl] + ld a, [de] + dec de ; de contains enemy or player MOVENUM + cp FLY_EFFECT + jr nz, .notFly + set Invulnerable, [hl] ; mon is now invulnerable to typical attacks (fly/dig) + ld b, TELEPORT ; load Teleport's animation +.notFly + ld a, [de] + cp DIG + jr nz, .notDigOrFly + set Invulnerable, [hl] ; mon is now invulnerable to typical attacks (fly/dig) + ld b, ANIM_C0 +.notDigOrFly + xor a + ld [wAnimationType], a + ld a, b + call PlayBattleAnimation + ld a, [de] + ld [wChargeMoveNum], a + ld hl, ChargeMoveEffectText + jp PrintText + +ChargeMoveEffectText: + TX_FAR _ChargeMoveEffectText + TX_ASM + ld a, [wChargeMoveNum] + cp RAZOR_WIND + ld hl, MadeWhirlwindText + jr z, .gotText + cp SOLARBEAM + ld hl, TookInSunlightText + jr z, .gotText + cp SKULL_BASH + ld hl, LoweredItsHeadText + jr z, .gotText + cp SKY_ATTACK + ld hl, SkyAttackGlowingText + jr z, .gotText + cp FLY + ld hl, FlewUpHighText + jr z, .gotText + cp DIG + ld hl, DugAHoleText +.gotText + ret + +MadeWhirlwindText: + TX_FAR _MadeWhirlwindText + db "@" + +TookInSunlightText: + TX_FAR _TookInSunlightText + db "@" + +LoweredItsHeadText: + TX_FAR _LoweredItsHeadText + db "@" + +SkyAttackGlowingText: + TX_FAR _SkyAttackGlowingText + db "@" + +FlewUpHighText: + TX_FAR _FlewUpHighText + db "@" + +DugAHoleText: + TX_FAR _DugAHoleText + db "@" + +TrappingEffect: + ld hl, wPlayerBattleStatus1 + ld de, wPlayerNumAttacksLeft + ld a, [H_WHOSETURN] + and a + jr z, .trappingEffect + ld hl, wEnemyBattleStatus1 + ld de, wEnemyNumAttacksLeft +.trappingEffect + bit UsingTrappingMove, [hl] + ret nz + call ClearHyperBeam ; since this effect is called before testing whether the move will hit, + ; the target won't need to recharge even if the trapping move missed + set UsingTrappingMove, [hl] ; mon is now using a trapping move + call BattleRandom ; 3/8 chance for 2 and 3 attacks, and 1/8 chance for 4 and 5 attacks + and $3 + cp $2 + jr c, .setTrappingCounter + call BattleRandom + and $3 +.setTrappingCounter + inc a + ld [de], a + ret + +MistEffect: + jpab MistEffect_ + +FocusEnergyEffect: + jpab FocusEnergyEffect_ + +RecoilEffect: + jpab RecoilEffect_ + +ConfusionSideEffect: + call BattleRandom + cp $19 ; ~10% chance + ret nc + jr ConfusionSideEffectSuccess + +ConfusionEffect: + call CheckTargetSubstitute + jr nz, ConfusionEffectFailed + call MoveHitTest + ld a, [wMoveMissed] + and a + jr nz, ConfusionEffectFailed + +ConfusionSideEffectSuccess: + ld a, [H_WHOSETURN] + and a + ld hl, wEnemyBattleStatus1 + ld bc, wEnemyConfusedCounter + ld a, [wPlayerMoveEffect] + jr z, .confuseTarget + ld hl, wPlayerBattleStatus1 + ld bc, wPlayerConfusedCounter + ld a, [wEnemyMoveEffect] +.confuseTarget + bit Confused, [hl] ; is mon confused? + jr nz, ConfusionEffectFailed + set Confused, [hl] ; mon is now confused + push af + call BattleRandom + and $3 + inc a + inc a + ld [bc], a ; confusion status will last 2-5 turns + pop af + cp CONFUSION_SIDE_EFFECT + call nz, PlayCurrentMoveAnimation2 + ld hl, BecameConfusedText + jp PrintText + +BecameConfusedText: + TX_FAR _BecameConfusedText + db "@" + +ConfusionEffectFailed: + cp CONFUSION_SIDE_EFFECT + ret z + ld c, 50 + call DelayFrames + jp ConditionalPrintButItFailed + +ParalyzeEffect: + jpab ParalyzeEffect_ + +SubstituteEffect: + jpab SubstituteEffect_ + +HyperBeamEffect: + ld hl, wPlayerBattleStatus2 + ld a, [H_WHOSETURN] + and a + jr z, .hyperBeamEffect + ld hl, wEnemyBattleStatus2 +.hyperBeamEffect + set NeedsToRecharge, [hl] ; mon now needs to recharge + ret + +ClearHyperBeam: + push hl + ld hl, wEnemyBattleStatus2 + ld a, [H_WHOSETURN] + and a + jr z, .playerTurn + ld hl, wPlayerBattleStatus2 +.playerTurn + res NeedsToRecharge, [hl] ; mon no longer needs to recharge + pop hl + ret + +RageEffect: + ld hl, wPlayerBattleStatus2 + ld a, [H_WHOSETURN] + and a + jr z, .player + ld hl, wEnemyBattleStatus2 +.player + set UsingRage, [hl] ; mon is now in "rage" mode + ret + +MimicEffect: + ld c, 50 + call DelayFrames + call MoveHitTest + ld a, [wMoveMissed] + and a + jr nz, .mimicMissed + ld a, [H_WHOSETURN] + and a + ld hl, wBattleMonMoves + ld a, [wPlayerBattleStatus1] + jr nz, .enemyTurn + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr nz, .letPlayerChooseMove + ld hl, wEnemyMonMoves + ld a, [wEnemyBattleStatus1] +.enemyTurn + bit Invulnerable, a + jr nz, .mimicMissed +.getRandomMove + push hl + call BattleRandom + and $3 + ld c, a + ld b, $0 + add hl, bc + ld a, [hl] + pop hl + and a + jr z, .getRandomMove + ld d, a + ld a, [H_WHOSETURN] + and a + ld hl, wBattleMonMoves + ld a, [wPlayerMoveListIndex] + jr z, .playerTurn + ld hl, wEnemyMonMoves + ld a, [wEnemyMoveListIndex] + jr .playerTurn +.letPlayerChooseMove + ld a, [wEnemyBattleStatus1] + bit Invulnerable, a + jr nz, .mimicMissed + ld a, [wCurrentMenuItem] + push af + ld a, $1 + ld [wMoveMenuType], a + call MoveSelectionMenu + call LoadScreenTilesFromBuffer1 + ld hl, wEnemyMonMoves + ld a, [wCurrentMenuItem] + ld c, a + ld b, $0 + add hl, bc + ld d, [hl] + pop af + ld hl, wBattleMonMoves +.playerTurn + ld c, a + ld b, $0 + add hl, bc + ld a, d + ld [hl], a + ld [wd11e], a + call GetMoveName + call PlayCurrentMoveAnimation + ld hl, MimicLearnedMoveText + jp PrintText +.mimicMissed + jp PrintButItFailedText_ + +MimicLearnedMoveText: + TX_FAR _MimicLearnedMoveText + db "@" + +LeechSeedEffect: + jpab LeechSeedEffect_ + +SplashEffect: + call PlayCurrentMoveAnimation + jp PrintNoEffectText + +DisableEffect: + call MoveHitTest + ld a, [wMoveMissed] + and a + jr nz, .moveMissed + ld de, wEnemyDisabledMove + ld hl, wEnemyMonMoves + ld a, [H_WHOSETURN] + and a + jr z, .disableEffect + ld de, wPlayerDisabledMove + ld hl, wBattleMonMoves +.disableEffect +; no effect if target already has a move disabled + ld a, [de] + and a + jr nz, .moveMissed +.pickMoveToDisable + push hl + call BattleRandom + and $3 + ld c, a + ld b, $0 + add hl, bc + ld a, [hl] + pop hl + and a + jr z, .pickMoveToDisable ; loop until a non-00 move slot is found + ld [wd11e], a ; store move number + push hl + ld a, [H_WHOSETURN] + and a + ld hl, wBattleMonPP + jr nz, .enemyTurn + ld a, [wLinkState] + cp LINK_STATE_BATTLING + pop hl ; wEnemyMonMoves + jr nz, .playerTurnNotLinkBattle +; .playerTurnLinkBattle + push hl + ld hl, wEnemyMonPP +.enemyTurn + push hl + ld a, [hli] + or [hl] + inc hl + or [hl] + inc hl + or [hl] + and $3f + pop hl ; wBattleMonPP or wEnemyMonPP + jr z, .moveMissedPopHL ; nothing to do if all moves have no PP left + add hl, bc + ld a, [hl] + pop hl + and a + jr z, .pickMoveToDisable ; pick another move if this one had 0 PP +.playerTurnNotLinkBattle +; non-link battle enemies have unlimited PP so the previous checks aren't needed + call BattleRandom + and $7 + inc a ; 1-8 turns disabled + inc c ; move 1-4 will be disabled + swap c + add c ; map disabled move to high nibble of wEnemyDisabledMove / wPlayerDisabledMove + ld [de], a + call PlayCurrentMoveAnimation2 + ld hl, wPlayerDisabledMoveNumber + ld a, [H_WHOSETURN] + and a + jr nz, .printDisableText + inc hl ; wEnemyDisabledMoveNumber +.printDisableText + ld a, [wd11e] ; move number + ld [hl], a + call GetMoveName + ld hl, MoveWasDisabledText + jp PrintText +.moveMissedPopHL + pop hl +.moveMissed + jp PrintButItFailedText_ + +MoveWasDisabledText: + TX_FAR _MoveWasDisabledText + db "@" + +PayDayEffect: + jpab PayDayEffect_ + +ConversionEffect: + jpab ConversionEffect_ + +HazeEffect: + jpab HazeEffect_ + +HealEffect: + jpab HealEffect_ + +TransformEffect: + jpab TransformEffect_ + +ReflectLightScreenEffect: + jpab ReflectLightScreenEffect_ + +NothingHappenedText: + TX_FAR _NothingHappenedText + db "@" + +PrintNoEffectText: + ld hl, NoEffectText + jp PrintText + +NoEffectText: + TX_FAR _NoEffectText + db "@" + +ConditionalPrintButItFailed: + ld a, [wMoveDidntMiss] + and a + ret nz ; return if the side effect failed, yet the attack was successful + +PrintButItFailedText_: + ld hl, ButItFailedText + jp PrintText + +ButItFailedText: + TX_FAR _ButItFailedText + db "@" + +PrintDidntAffectText: + ld hl, DidntAffectText + jp PrintText + +DidntAffectText: + TX_FAR _DidntAffectText + db "@" + +IsUnaffectedText: + TX_FAR _IsUnaffectedText + db "@" + +PrintMayNotAttackText: + ld hl, ParalyzedMayNotAttackText + jp PrintText + +ParalyzedMayNotAttackText: + TX_FAR _ParalyzedMayNotAttackText + db "@" + +CheckTargetSubstitute: + push hl + ld hl, wEnemyBattleStatus2 + ld a, [H_WHOSETURN] + and a + jr z, .next1 + ld hl, wPlayerBattleStatus2 +.next1 + bit HasSubstituteUp, [hl] + pop hl + ret + +PlayCurrentMoveAnimation2: +; animation at MOVENUM will be played unless MOVENUM is 0 +; plays wAnimationType 3 or 6 + ld a, [H_WHOSETURN] + and a + ld a, [wPlayerMoveNum] + jr z, .notEnemyTurn + ld a, [wEnemyMoveNum] +.notEnemyTurn + and a + ret z + +PlayBattleAnimation2: +; play animation ID at a and animation type 6 or 3 + ld [wAnimationID], a + ld a, [H_WHOSETURN] + and a + ld a, $6 + jr z, .storeAnimationType + ld a, $3 +.storeAnimationType + ld [wAnimationType], a + jp PlayBattleAnimationGotID + +PlayCurrentMoveAnimation: +; animation at MOVENUM will be played unless MOVENUM is 0 +; resets wAnimationType + xor a + ld [wAnimationType], a + ld a, [H_WHOSETURN] + and a + ld a, [wPlayerMoveNum] + jr z, .notEnemyTurn + ld a, [wEnemyMoveNum] +.notEnemyTurn + and a + ret z + +PlayBattleAnimation: +; play animation ID at a and predefined animation type + ld [wAnimationID], a + +PlayBattleAnimationGotID: +; play animation at wAnimationID + push hl + push de + push bc + predef MoveAnimation + pop bc + pop de + pop hl + ret diff --git a/de/engine/battle/end_of_battle.asm b/de/engine/battle/end_of_battle.asm new file mode 100755 index 00000000..190992dc --- /dev/null +++ b/de/engine/battle/end_of_battle.asm @@ -0,0 +1,91 @@ +EndOfBattle: + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr nz, .notLinkBattle +; link battle + ld a, [wEnemyMonPartyPos] + ld hl, wEnemyMon1Status + ld bc, wEnemyMon2 - wEnemyMon1 + call AddNTimes + ld a, [wEnemyMonStatus] + ld [hl], a + call ClearScreen + callab DisplayLinkBattleVersusTextBox + ld a, [wBattleResult] + cp 1 + ld de, YouWinText + jr c, .unk + ld de, YouLoseText + jr z, .unk + ld de, DrawText + coord hl, 4, 8 + jr .placeWinOrLoseString +.unk + coord hl, 6, 8 +.placeWinOrLoseString + call PlaceString + ld c, 200 + call DelayFrames + jr .evolution +.notLinkBattle + ld a, [wBattleResult] + and a + jr nz, .resetVariables + ld hl, wTotalPayDayMoney + ld a, [hli] + or [hl] + inc hl + or [hl] + jr z, .evolution ; if pay day money is 0, jump + ld de, wPlayerMoney + 2 + ld c, $3 + predef AddBCDPredef + ld hl, PickUpPayDayMoneyText + call PrintText +.evolution + xor a + ld [wForceEvolution], a + predef EvolutionAfterBattle +.resetVariables + xor a + ld [wLowHealthAlarm], a ;disable low health alarm + ld [wChannelSoundIDs + Ch4], a + ld [wIsInBattle], a + ld [wBattleType], a + ld [wMoveMissed], a + ld [wCurOpponent], a + ld [wForcePlayerToChooseMon], a + ld [wNumRunAttempts], a + ld [wEscapedFromBattle], a + ld hl, wPartyAndBillsPCSavedMenuItem + ld [hli], a + ld [hli], a + ld [hli], a + ld [hl], a + ld [wListScrollOffset], a + ld hl, wPlayerStatsToDouble + ld b, $18 +.loop + ld [hli], a + dec b + jr nz, .loop + ld hl, wd72c + set 0, [hl] + call WaitForSoundToFinish + call GBPalWhiteOut + ld a, $ff + ld [wDestinationWarpID], a + ret + +YouWinText: + db "GEWONNEN@" + +YouLoseText: + db "VERLOREN@" + +DrawText: + db "UNENTSCHIEDEN@" + +PickUpPayDayMoneyText: + TX_FAR _PickUpPayDayMoneyText + db "@" diff --git a/de/engine/battle/link_battle_versus_text.asm b/de/engine/battle/link_battle_versus_text.asm new file mode 100644 index 00000000..9e5f89cb --- /dev/null +++ b/de/engine/battle/link_battle_versus_text.asm @@ -0,0 +1,23 @@ +; display "[player] VS [enemy]" text box with pokeballs representing their parties next to the names +DisplayLinkBattleVersusTextBox: + call LoadTextBoxTilePatterns + coord hl, 3, 4 + ld b, 7 + ld c, 13 + call TextBoxBorder + coord hl, 4, 5 + ld de, wPlayerName + call PlaceString + coord hl, 4, 10 + ld de, wLinkEnemyTrainerName + call PlaceString +; place bold "VS" tiles between the names + coord hl, 9, 8 + ld a, "V" + ldi [hl], a + ld [hl], "S" + xor a + ld [wUpdateSpritesEnabled], a + callab SetupPlayerAndEnemyPokeballs + ld c, 150 + jp DelayFrames diff --git a/de/engine/battle/save_trainer_name.asm b/de/engine/battle/save_trainer_name.asm new file mode 100644 index 00000000..50f8f052 --- /dev/null +++ b/de/engine/battle/save_trainer_name.asm @@ -0,0 +1,112 @@ +SaveTrainerName: + ld hl,TrainerNamePointers + ld a,[wTrainerClass] + dec a + ld c,a + ld b,0 + add hl,bc + add hl,bc + ld a,[hli] + ld h,[hl] + ld l,a + ld de,wcd6d +.CopyCharacter + ld a,[hli] + ld [de],a + inc de + cp "@" + jr nz,.CopyCharacter + ret + +TrainerNamePointers: +; what is the point of these? + dw YoungsterName + dw BugCatcherName + dw LassName + dw wTrainerName + dw JrTrainerMName + dw JrTrainerFName + dw PokemaniacName + dw SuperNerdName + dw wTrainerName + dw wTrainerName + dw BurglarName + dw EngineerName + dw JugglerXName + dw wTrainerName + dw SwimmerName + dw wTrainerName + dw wTrainerName + dw BeautyName + dw wTrainerName + dw RockerName + dw JugglerName + dw wTrainerName + dw wTrainerName + dw BlackbeltName + dw wTrainerName + dw ProfOakName + dw ChiefName + dw ScientistName + dw wTrainerName + dw RocketName + dw CooltrainerMName + dw CooltrainerFName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + +YoungsterName: + db "TEENAGER@" +BugCatcherName: + db "KÄFERSAMMLER@" +LassName: + db "GÖRE@" +JrTrainerMName: + db "PFADFINDER@" +JrTrainerFName: + db "PFADFINDERIN@" +PokemaniacName: + db "#MANIAC@" +SuperNerdName: + db "STREBER@" +BurglarName: + db "DIEB@" +EngineerName: + db "MECHANIKER@" +JugglerXName: + db "JONGLEUR@" +SwimmerName: + db "SCHWIMMER@" +BeautyName: + db "SCHÖNHEIT@" +RockerName: + db "ROCKER@" +JugglerName: + db "JONGLEUR@" +BlackbeltName: + db "SCHWARZGURT@" +ProfOakName: + db "PROF.EICH@" +ChiefName: + db "CHIEF@" +ScientistName: + db "FORSCHER@" +RocketName: + db "ROCKET@" +CooltrainerMName: + db "TRAINER@" +CooltrainerFName: + db "TRAINERIN@" diff --git a/de/engine/cable_club.asm b/de/engine/cable_club.asm new file mode 100755 index 00000000..6e0eeb39 --- /dev/null +++ b/de/engine/cable_club.asm @@ -0,0 +1,977 @@ +; performs the appropriate action when the player uses the gameboy on the table in the Colosseum or Trade Center +; In the Colosseum, it starts a battle. In the Trade Center, it displays the trade selection screen. +; Before doing either action, it swaps random numbers, trainer names and party data with the other gameboy. +CableClub_DoBattleOrTrade: + ld c, 80 + call DelayFrames + call ClearScreen + call UpdateSprites + call LoadFontTilePatterns + call LoadHpBarAndStatusTilePatterns + call LoadTrainerInfoTextBoxTiles + coord hl, 3, 8 + ld b, 2 + ld c, 13 + call CableClub_TextBoxBorder + coord hl, 4, 10 + ld de, PleaseWaitString + call PlaceString + ld hl, wPlayerNumHits + xor a + ld [hli], a + ld [hl], $50 + ; fall through + +; This is called after completing a trade. +CableClub_DoBattleOrTradeAgain: + ld hl, wSerialPlayerDataBlock + ld a, SERIAL_PREAMBLE_BYTE + ld b, 6 +.writePlayerDataBlockPreambleLoop + ld [hli], a + dec b + jr nz, .writePlayerDataBlockPreambleLoop + ld hl, wSerialRandomNumberListBlock + ld a, SERIAL_PREAMBLE_BYTE + ld b, 7 +.writeRandomNumberListPreambleLoop + ld [hli], a + dec b + jr nz, .writeRandomNumberListPreambleLoop + ld b, 10 +.generateRandomNumberListLoop + call Random + cp SERIAL_PREAMBLE_BYTE ; all the random numbers have to be less than the preamble byte + jr nc, .generateRandomNumberListLoop + ld [hli], a + dec b + jr nz, .generateRandomNumberListLoop + ld hl, wSerialPartyMonsPatchList + ld a, SERIAL_PREAMBLE_BYTE + ld [hli], a + ld [hli], a + ld [hli], a + ld b, $c8 + xor a +.zeroPlayerDataPatchListLoop + ld [hli], a + dec b + jr nz, .zeroPlayerDataPatchListLoop + ld hl, wGrassRate + ld bc, wTrainerHeaderPtr - wGrassRate +.zeroEnemyPartyLoop + xor a + ld [hli], a + dec bc + ld a, b + or c + jr nz, .zeroEnemyPartyLoop + ld hl, wPartyMons - 1 + ld de, wSerialPartyMonsPatchList + 10 + ld bc, 0 +.patchPartyMonsLoop + inc c + ld a, c + cp SERIAL_PREAMBLE_BYTE + jr z, .startPatchListPart2 + ld a, b + dec a ; are we in part 2 of the patch list? + jr nz, .checkPlayerDataByte ; jump if in part 1 +; if we're in part 2 + ld a, c + cp (wPartyMonOT - (wPartyMons - 1)) - (SERIAL_PREAMBLE_BYTE - 1) + jr z, .finishedPatchingPlayerData +.checkPlayerDataByte + inc hl + ld a, [hl] + cp SERIAL_NO_DATA_BYTE + jr nz, .patchPartyMonsLoop +; if the player data byte matches SERIAL_NO_DATA_BYTE, patch it with $FF and record the offset in the patch list + ld a, c + ld [de], a + inc de + ld [hl], $ff + jr .patchPartyMonsLoop +.startPatchListPart2 + ld a, SERIAL_PATCH_LIST_PART_TERMINATOR + ld [de], a ; end of part 1 + inc de + lb bc, 1, 0 + jr .patchPartyMonsLoop +.finishedPatchingPlayerData + ld a, SERIAL_PATCH_LIST_PART_TERMINATOR + ld [de], a ; end of part 2 + call Serial_SyncAndExchangeNybble + ld a, [hSerialConnectionStatus] + cp USING_INTERNAL_CLOCK + jr nz, .skipSendingTwoZeroBytes +; if using internal clock +; send two zero bytes for syncing purposes? + call Delay3 + xor a + ld [hSerialSendData], a + ld a, START_TRANSFER_INTERNAL_CLOCK + ld [rSC], a + call DelayFrame + xor a + ld [hSerialSendData], a + ld a, START_TRANSFER_INTERNAL_CLOCK + ld [rSC], a +.skipSendingTwoZeroBytes + call Delay3 + ld a, (1 << SERIAL) + ld [rIE], a + ld hl, wSerialRandomNumberListBlock + ld de, wSerialOtherGameboyRandomNumberListBlock + ld bc, $11 + call Serial_ExchangeBytes + ld a, SERIAL_NO_DATA_BYTE + ld [de], a + ld hl, wSerialPlayerDataBlock + ld de, wSerialEnemyDataBlock + ld bc, $1a8 + call Serial_ExchangeBytes + ld a, SERIAL_NO_DATA_BYTE + ld [de], a + ld hl, wSerialPartyMonsPatchList + ld de, wSerialEnemyMonsPatchList + ld bc, $c8 + call Serial_ExchangeBytes + ld a, (1 << SERIAL) | (1 << TIMER) | (1 << VBLANK) + ld [rIE], a + ld a, $ff + call PlaySound + ld a, [hSerialConnectionStatus] + cp USING_INTERNAL_CLOCK + jr z, .skipCopyingRandomNumberList ; the list generated by the gameboy clocking the connection is used by both gameboys + ld hl, wSerialOtherGameboyRandomNumberListBlock +.findStartOfRandomNumberListLoop + ld a, [hli] + and a + jr z, .findStartOfRandomNumberListLoop + cp SERIAL_PREAMBLE_BYTE + jr z, .findStartOfRandomNumberListLoop + cp SERIAL_NO_DATA_BYTE + jr z, .findStartOfRandomNumberListLoop + dec hl + ld de, wLinkBattleRandomNumberList + ld c, 10 +.copyRandomNumberListLoop + ld a, [hli] + cp SERIAL_NO_DATA_BYTE + jr z, .copyRandomNumberListLoop + ld [de], a + inc de + dec c + jr nz, .copyRandomNumberListLoop +.skipCopyingRandomNumberList + ld hl, wSerialEnemyDataBlock + 3 +.findStartOfEnemyNameLoop + ld a, [hli] + and a + jr z, .findStartOfEnemyNameLoop + cp SERIAL_PREAMBLE_BYTE + jr z, .findStartOfEnemyNameLoop + cp SERIAL_NO_DATA_BYTE + jr z, .findStartOfEnemyNameLoop + dec hl + ld de, wLinkEnemyTrainerName + ld c, NAME_LENGTH +.copyEnemyNameLoop + ld a, [hli] + cp SERIAL_NO_DATA_BYTE + jr z, .copyEnemyNameLoop + ld [de], a + inc de + dec c + jr nz, .copyEnemyNameLoop + ld de, wEnemyPartyCount + ld bc, wTrainerHeaderPtr - wEnemyPartyCount +.copyEnemyPartyLoop + ld a, [hli] + cp SERIAL_NO_DATA_BYTE + jr z, .copyEnemyPartyLoop + ld [de], a + inc de + dec bc + ld a, b + or c + jr nz, .copyEnemyPartyLoop + ld de, wSerialPartyMonsPatchList + ld hl, wPartyMons + ld c, 2 ; patch list has 2 parts +.unpatchPartyMonsLoop + ld a, [de] + inc de + and a + jr z, .unpatchPartyMonsLoop + cp SERIAL_PREAMBLE_BYTE + jr z, .unpatchPartyMonsLoop + cp SERIAL_NO_DATA_BYTE + jr z, .unpatchPartyMonsLoop + cp SERIAL_PATCH_LIST_PART_TERMINATOR + jr z, .finishedPartyMonsPatchListPart + push hl + push bc + ld b, 0 + dec a + ld c, a + add hl, bc + ld a, SERIAL_NO_DATA_BYTE + ld [hl], a + pop bc + pop hl + jr .unpatchPartyMonsLoop +.finishedPartyMonsPatchListPart + ld hl, wPartyMons + (SERIAL_PREAMBLE_BYTE - 1) + dec c ; is there another part? + jr nz, .unpatchPartyMonsLoop + ld de, wSerialEnemyMonsPatchList + ld hl, wEnemyMons + ld c, 2 ; patch list has 2 parts +.unpatchEnemyMonsLoop + ld a, [de] + inc de + and a + jr z, .unpatchEnemyMonsLoop + cp SERIAL_PREAMBLE_BYTE + jr z, .unpatchEnemyMonsLoop + cp SERIAL_NO_DATA_BYTE + jr z, .unpatchEnemyMonsLoop + cp SERIAL_PATCH_LIST_PART_TERMINATOR + jr z, .finishedEnemyMonsPatchListPart + push hl + push bc + ld b, 0 + dec a + ld c, a + add hl, bc + ld a, SERIAL_NO_DATA_BYTE + ld [hl], a + pop bc + pop hl + jr .unpatchEnemyMonsLoop +.finishedEnemyMonsPatchListPart + ld hl, wEnemyMons + (SERIAL_PREAMBLE_BYTE - 1) + dec c + jr nz, .unpatchEnemyMonsLoop + ld a, wEnemyMonOT % $100 + ld [wUnusedCF8D], a + ld a, wEnemyMonOT / $100 + ld [wUnusedCF8D + 1], a + xor a + ld [wTradeCenterPointerTableIndex], a + ld a, $ff + call PlaySound + ld a, [hSerialConnectionStatus] + cp USING_INTERNAL_CLOCK + ld c, 66 + call z, DelayFrames ; delay if using internal clock + ld a, [wLinkState] + cp LINK_STATE_START_BATTLE + ld a, LINK_STATE_TRADING + ld [wLinkState], a + jr nz, .trading + ld a, LINK_STATE_BATTLING + ld [wLinkState], a + ld a, OPP_SONY1 + ld [wCurOpponent], a + call ClearScreen + call Delay3 + ld hl, wOptions + res 7, [hl] + predef InitOpponent + predef HealParty + jp ReturnToCableClubRoom +.trading + ld c, BANK(Music_GameCorner) + ld a, MUSIC_GAME_CORNER + call PlayMusic + jr CallCurrentTradeCenterFunction + +PleaseWaitString: + db "BITTE WARTEN!@" + +CallCurrentTradeCenterFunction: + ld hl, TradeCenterPointerTable + ld b, 0 + ld a, [wTradeCenterPointerTableIndex] + cp $ff + jp z, DisplayTitleScreen + add a + ld c, a + add hl, bc + ld a, [hli] + ld h, [hl] + ld l, a + jp hl + +TradeCenter_SelectMon: + call ClearScreen + call LoadTrainerInfoTextBoxTiles + call TradeCenter_DrawPartyLists + call TradeCenter_DrawCancelBox + xor a + ld hl, wSerialSyncAndExchangeNybbleReceiveData + ld [hli], a + ld [hli], a + ld [hli], a + ld [hl], a + ld [wMenuWatchMovingOutOfBounds], a + ld [wCurrentMenuItem], a + ld [wLastMenuItem], a + ld [wMenuJoypadPollCount], a + inc a + ld [wSerialExchangeNybbleSendData], a + jp .playerMonMenu +.enemyMonMenu + xor a + ld [wMenuWatchMovingOutOfBounds], a + inc a + ld [wWhichTradeMonSelectionMenu], a + ld a, D_DOWN | D_LEFT | A_BUTTON + ld [wMenuWatchedKeys], a + ld a, [wEnemyPartyCount] + ld [wMaxMenuItem], a + ld a, 9 + ld [wTopMenuItemY], a + ld a, 1 + ld [wTopMenuItemX], a +.enemyMonMenu_HandleInput + ld hl, hFlags_0xFFF6 + set 1, [hl] + call HandleMenuInput + ld hl, hFlags_0xFFF6 + res 1, [hl] + and a + jp z, .getNewInput + bit 0, a ; A button pressed? + jr z, .enemyMonMenu_ANotPressed +; if A button pressed + ld a, [wMaxMenuItem] + ld c, a + ld a, [wCurrentMenuItem] + cp c + jr c, .displayEnemyMonStats + ld a, [wMaxMenuItem] + dec a + ld [wCurrentMenuItem], a +.displayEnemyMonStats + ld a, INIT_ENEMYOT_LIST + ld [wInitListType], a + callab InitList ; the list isn't used + ld hl, wEnemyMons + call TradeCenter_DisplayStats + jp .getNewInput +.enemyMonMenu_ANotPressed + bit 5, a ; Left pressed? + jr z, .enemyMonMenu_LeftNotPressed +; if Left pressed, switch back to the player mon menu + xor a ; player mon menu + ld [wWhichTradeMonSelectionMenu], a + ld a, [wMenuCursorLocation] + ld l, a + ld a, [wMenuCursorLocation + 1] + ld h, a + ld a, [wTileBehindCursor] + ld [hl], a + ld a, [wCurrentMenuItem] + ld b, a + ld a, [wPartyCount] + dec a + cp b + jr nc, .playerMonMenu + ld [wCurrentMenuItem], a + jr .playerMonMenu +.enemyMonMenu_LeftNotPressed + bit 7, a ; Down pressed? + jp z, .getNewInput + jp .selectedCancelMenuItem ; jump if Down pressed +.playerMonMenu + xor a ; player mon menu + ld [wWhichTradeMonSelectionMenu], a + ld [wMenuWatchMovingOutOfBounds], a + ld a, D_DOWN | D_RIGHT | A_BUTTON + ld [wMenuWatchedKeys], a + ld a, [wPartyCount] + ld [wMaxMenuItem], a + ld a, 1 + ld [wTopMenuItemY], a + ld a, 1 + ld [wTopMenuItemX], a + coord hl, 1, 1 + lb bc, 6, 1 + call ClearScreenArea +.playerMonMenu_HandleInput + ld hl, hFlags_0xFFF6 + set 1, [hl] + call HandleMenuInput + ld hl, hFlags_0xFFF6 + res 1, [hl] + and a ; was anything pressed? + jr nz, .playerMonMenu_SomethingPressed + jp .getNewInput +.playerMonMenu_SomethingPressed + bit 0, a ; A button pressed? + jr z, .playerMonMenu_ANotPressed + jp .chosePlayerMon ; jump if A button pressed +; unreachable code + ld a, INIT_PLAYEROT_LIST + ld [wInitListType], a + callab InitList ; the list isn't used + call TradeCenter_DisplayStats + jp .getNewInput +.playerMonMenu_ANotPressed + bit 4, a ; Right pressed? + jr z, .playerMonMenu_RightNotPressed +; if Right pressed, switch to the enemy mon menu + ld a, $1 ; enemy mon menu + ld [wWhichTradeMonSelectionMenu], a + ld a, [wMenuCursorLocation] + ld l, a + ld a, [wMenuCursorLocation + 1] + ld h, a + ld a, [wTileBehindCursor] + ld [hl], a + ld a, [wCurrentMenuItem] + ld b, a + ld a, [wEnemyPartyCount] + dec a + cp b + jr nc, .notPastLastEnemyMon +; when switching to the enemy mon menu, if the menu selection would be past the last enemy mon, select the last enemy mon + ld [wCurrentMenuItem], a +.notPastLastEnemyMon + jp .enemyMonMenu +.playerMonMenu_RightNotPressed + bit 7, a ; Down pressed? + jr z, .getNewInput + jp .selectedCancelMenuItem ; jump if Down pressed +.getNewInput + ld a, [wWhichTradeMonSelectionMenu] + and a + jp z, .playerMonMenu_HandleInput + jp .enemyMonMenu_HandleInput +.chosePlayerMon + call SaveScreenTilesToBuffer1 + call PlaceUnfilledArrowMenuCursor + ld a, [wMaxMenuItem] + ld c, a + ld a, [wCurrentMenuItem] + cp c + jr c, .displayStatsTradeMenu + ld a, [wMaxMenuItem] + dec a +.displayStatsTradeMenu + push af + coord hl, 0, 14 + ld b, 2 + ld c, 18 + call CableClub_TextBoxBorder + coord hl, 2, 16 + ld de, .statsTrade + call PlaceString + xor a + ld [wCurrentMenuItem], a + ld [wLastMenuItem], a + ld [wMenuJoypadPollCount], a + ld [wMaxMenuItem], a + ld a, 16 + ld [wTopMenuItemY], a +.selectStatsMenuItem + ld a, " " + Coorda 11, 16 + ld a, D_RIGHT | B_BUTTON | A_BUTTON + ld [wMenuWatchedKeys], a + ld a, 1 + ld [wTopMenuItemX], a + call HandleMenuInput + bit 4, a ; Right pressed? + jr nz, .selectTradeMenuItem + bit 1, a ; B button pressed? + jr z, .displayPlayerMonStats +.cancelPlayerMonChoice + pop af + ld [wCurrentMenuItem], a + call LoadScreenTilesFromBuffer1 + jp .playerMonMenu +.selectTradeMenuItem + ld a, " " + Coorda 1, 16 + ld a, D_LEFT | B_BUTTON | A_BUTTON + ld [wMenuWatchedKeys], a + ld a, 11 + ld [wTopMenuItemX], a + call HandleMenuInput + bit 5, a ; Left pressed? + jr nz, .selectStatsMenuItem + bit 1, a ; B button pressed? + jr nz, .cancelPlayerMonChoice + jr .choseTrade +.displayPlayerMonStats + pop af + ld [wCurrentMenuItem], a + ld a, INIT_PLAYEROT_LIST + ld [wInitListType], a + callab InitList ; the list isn't used + call TradeCenter_DisplayStats + call LoadScreenTilesFromBuffer1 + jp .playerMonMenu +.choseTrade + call PlaceUnfilledArrowMenuCursor + pop af + ld [wCurrentMenuItem], a + ld [wTradingWhichPlayerMon], a + ld [wSerialExchangeNybbleSendData], a + call Serial_PrintWaitingTextAndSyncAndExchangeNybble + ld a, [wSerialSyncAndExchangeNybbleReceiveData] + cp $f + jp z, CallCurrentTradeCenterFunction ; go back to the beginning of the trade selection menu if the other person cancelled + ld [wTradingWhichEnemyMon], a + call TradeCenter_PlaceSelectedEnemyMonMenuCursor + ld a, $1 ; TradeCenter_Trade + ld [wTradeCenterPointerTableIndex], a + jp CallCurrentTradeCenterFunction +.statsTrade + db "STATUS TAUSCH@" +.selectedCancelMenuItem + ld a, [wCurrentMenuItem] + ld b, a + ld a, [wMaxMenuItem] + cp b + jp nz, .getNewInput + ld a, [wMenuCursorLocation] + ld l, a + ld a, [wMenuCursorLocation + 1] + ld h, a + ld a, " " + ld [hl], a +.cancelMenuItem_Loop + ld a, "▶" ; filled arrow cursor + Coorda 1, 16 +.cancelMenuItem_JoypadLoop + call JoypadLowSensitivity + ld a, [hJoy5] + and a ; pressed anything? + jr z, .cancelMenuItem_JoypadLoop + bit 0, a ; A button pressed? + jr nz, .cancelMenuItem_APressed + bit 6, a ; Up pressed? + jr z, .cancelMenuItem_JoypadLoop +; if Up pressed + ld a, " " + Coorda 1, 16 + ld a, [wPartyCount] + dec a + ld [wCurrentMenuItem], a + jp .playerMonMenu +.cancelMenuItem_APressed + ld a, "▷" ; unfilled arrow cursor + Coorda 1, 16 + ld a, $f + ld [wSerialExchangeNybbleSendData], a + call Serial_PrintWaitingTextAndSyncAndExchangeNybble + ld a, [wSerialSyncAndExchangeNybbleReceiveData] + cp $f ; did the other person choose Cancel too? + jr nz, .cancelMenuItem_Loop + ; fall through + +ReturnToCableClubRoom: + call GBPalWhiteOutWithDelay3 + ld hl, wFontLoaded + ld a, [hl] + push af + push hl + res 0, [hl] + xor a + ld [wd72d], a + dec a + ld [wDestinationWarpID], a + call LoadMapData + callba ClearVariablesOnEnterMap + pop hl + pop af + ld [hl], a + call GBFadeInFromWhite + ret + +TradeCenter_DrawCancelBox: + coord hl, 8, 15 + ld a, $7e + ld bc, 2 * SCREEN_WIDTH + 12 + call FillMemory + coord hl, 0, 15 + ld b, 1 + ld c, 12 + call CableClub_TextBoxBorder + coord hl, 2, 16 + ld de, CancelTextString + jp PlaceString + +CancelTextString: + db "ABBRECHEN@" + +TradeCenter_PlaceSelectedEnemyMonMenuCursor: + ld a, [wSerialSyncAndExchangeNybbleReceiveData] + coord hl, 1, 9 + ld bc, SCREEN_WIDTH + call AddNTimes + ld [hl], "▷" ; cursor + ret + +TradeCenter_DisplayStats: + ld a, [wCurrentMenuItem] + ld [wWhichPokemon], a + predef StatusScreen + predef StatusScreen2 + call GBPalNormal + call LoadTrainerInfoTextBoxTiles + call TradeCenter_DrawPartyLists + jp TradeCenter_DrawCancelBox + +TradeCenter_DrawPartyLists: + coord hl, 0, 0 + ld b, 6 + ld c, 18 + call CableClub_TextBoxBorder + coord hl, 0, 8 + ld b, 6 + ld c, 18 + call CableClub_TextBoxBorder + coord hl, 5, 0 + ld de, wPlayerName + call PlaceString + coord hl, 5, 8 + ld de, wLinkEnemyTrainerName + call PlaceString + coord hl, 2, 1 + ld de, wPartySpecies + call TradeCenter_PrintPartyListNames + coord hl, 2, 9 + ld de, wEnemyPartyMons + ; fall through + +TradeCenter_PrintPartyListNames: + ld c, $0 +.loop + ld a, [de] + cp $ff + ret z + ld [wd11e], a + push bc + push hl + push de + push hl + ld a, c + ld [$ff95], a + call GetMonName + pop hl + call PlaceString + pop de + inc de + pop hl + ld bc, 20 + add hl, bc + pop bc + inc c + jr .loop + +TradeCenter_Trade: + ld c, 100 + call DelayFrames + xor a + ld [wSerialExchangeNybbleSendData + 1], a ; unnecessary + ld [wSerialExchangeNybbleReceiveData], a + ld [wMenuWatchMovingOutOfBounds], a + ld [wMenuJoypadPollCount], a + coord hl, 0, 12 + ld b, 4 + ld c, 18 + call CableClub_TextBoxBorder + ld a, [wTradingWhichPlayerMon] + ld hl, wPartySpecies + ld c, a + ld b, 0 + add hl, bc + ld a, [hl] + ld [wd11e], a + call GetMonName + ld hl, wcd6d + ld de, wNameOfPlayerMonToBeTraded + ld bc, NAME_LENGTH + call CopyData + ld a, [wTradingWhichEnemyMon] + ld hl, wEnemyPartyMons + ld c, a + ld b, 0 + add hl, bc + ld a, [hl] + ld [wd11e], a + call GetMonName + ld hl, WillBeTradedText + coord bc, 1, 14 + call TextCommandProcessor + call SaveScreenTilesToBuffer1 + coord hl, 10, 7 + lb bc, 8, 11 + ld a, TRADE_CANCEL_MENU + ld [wTwoOptionMenuID], a + ld a, TWO_OPTION_MENU + ld [wTextBoxID], a + call DisplayTextBoxID + call LoadScreenTilesFromBuffer1 + ld a, [wCurrentMenuItem] + and a + jr z, .tradeConfirmed +; if trade cancelled + ld a, $1 + ld [wSerialExchangeNybbleSendData], a + coord hl, 0, 12 + ld b, 4 + ld c, 18 + call CableClub_TextBoxBorder + coord hl, 1, 14 + ld de, TradeCanceled + call PlaceString + call Serial_PrintWaitingTextAndSyncAndExchangeNybble + jp .tradeCancelled +.tradeConfirmed + ld a, $2 + ld [wSerialExchangeNybbleSendData], a + call Serial_PrintWaitingTextAndSyncAndExchangeNybble + ld a, [wSerialSyncAndExchangeNybbleReceiveData] + dec a ; did the other person cancel? + jr nz, .doTrade +; if the other person cancelled + coord hl, 0, 12 + ld b, 4 + ld c, 18 + call CableClub_TextBoxBorder + coord hl, 1, 14 + ld de, TradeCanceled + call PlaceString + jp .tradeCancelled +.doTrade + ld a, [wTradingWhichPlayerMon] + ld hl, wPartyMonOT + call SkipFixedLengthTextEntries + ld de, wTradedPlayerMonOT + ld bc, NAME_LENGTH + call CopyData + ld hl, wPartyMon1Species + ld a, [wTradingWhichPlayerMon] + ld bc, wPartyMon2 - wPartyMon1 + call AddNTimes + ld bc, wPartyMon1OTID - wPartyMon1 + add hl, bc + ld a, [hli] + ld [wTradedPlayerMonOTID], a + ld a, [hl] + ld [wTradedPlayerMonOTID + 1], a + ld a, [wTradingWhichEnemyMon] + ld hl, wEnemyMonOT + call SkipFixedLengthTextEntries + ld de, wTradedEnemyMonOT + ld bc, NAME_LENGTH + call CopyData + ld hl, wEnemyMons + ld a, [wTradingWhichEnemyMon] + ld bc, wEnemyMon2 - wEnemyMon1 + call AddNTimes + ld bc, wEnemyMon1OTID - wEnemyMon1 + add hl, bc + ld a, [hli] + ld [wTradedEnemyMonOTID], a + ld a, [hl] + ld [wTradedEnemyMonOTID + 1], a + ld a, [wTradingWhichPlayerMon] + ld [wWhichPokemon], a + ld hl, wPartySpecies + ld b, 0 + ld c, a + add hl, bc + ld a, [hl] + ld [wTradedPlayerMonSpecies], a + xor a + ld [wRemoveMonFromBox], a + call RemovePokemon + ld a, [wTradingWhichEnemyMon] + ld c, a + ld [wWhichPokemon], a + ld hl, wEnemyPartyMons + ld d, 0 + ld e, a + add hl, de + ld a, [hl] + ld [wcf91], a + ld hl, wEnemyMons + ld a, c + ld bc, wEnemyMon2 - wEnemyMon1 + call AddNTimes + ld de, wLoadedMon + ld bc, wEnemyMon2 - wEnemyMon1 + call CopyData + call AddEnemyMonToPlayerParty + ld a, [wPartyCount] + dec a + ld [wWhichPokemon], a + ld a, $1 + ld [wForceEvolution], a + ld a, [wTradingWhichEnemyMon] + ld hl, wEnemyPartyMons + ld b, 0 + ld c, a + add hl, bc + ld a, [hl] + ld [wTradedEnemyMonSpecies], a + ld a, 10 + ld [wAudioFadeOutControl], a + ld a, $2 + ld [wAudioSavedROMBank], a + ld a, MUSIC_SAFARI_ZONE + ld [wNewSoundID], a + call PlaySound + ld c, 100 + call DelayFrames + call ClearScreen + call LoadHpBarAndStatusTilePatterns + xor a + ld [wUnusedCC5B], a + ld a, [hSerialConnectionStatus] + cp USING_EXTERNAL_CLOCK + jr z, .usingExternalClock + predef InternalClockTradeAnim + jr .tradeCompleted +.usingExternalClock + predef ExternalClockTradeAnim +.tradeCompleted + callab TryEvolvingMon + call ClearScreen + call LoadTrainerInfoTextBoxTiles + call Serial_PrintWaitingTextAndSyncAndExchangeNybble + ld c, 40 + call DelayFrames + coord hl, 0, 12 + ld b, 4 + ld c, 18 + call CableClub_TextBoxBorder + coord hl, 1, 14 + ld de, TradeCompleted + call PlaceString + predef SaveSAVtoSRAM2 + ld c, 50 + call DelayFrames + xor a + ld [wTradeCenterPointerTableIndex], a + jp CableClub_DoBattleOrTradeAgain +.tradeCancelled + ld c, 100 + call DelayFrames + xor a ; TradeCenter_SelectMon + ld [wTradeCenterPointerTableIndex], a + jp CallCurrentTradeCenterFunction + +WillBeTradedText: + TX_FAR _WillBeTradedText + db "@" + +TradeCompleted: + db "TAUSCH VOLLZOGEN!@" + +TradeCanceled: + db "Schade! Der tausch" + next "wurde abgebrochen!@" + +TradeCenterPointerTable: + dw TradeCenter_SelectMon + dw TradeCenter_Trade + +CableClub_Run: + ld a, [wLinkState] + cp LINK_STATE_START_TRADE + jr z, .doBattleOrTrade + cp LINK_STATE_START_BATTLE + jr z, .doBattleOrTrade + cp LINK_STATE_RESET ; this is never used + ret nz + predef EmptyFunc3 + jp Init +.doBattleOrTrade + call CableClub_DoBattleOrTrade + ld hl, Club_GFX + ld a, h + ld [wTilesetGfxPtr + 1], a + ld a, l + ld [wTilesetGfxPtr], a + ld a, Bank(Club_GFX) + ld [wTilesetBank], a + ld hl, Club_Coll + ld a, h + ld [wTilesetCollisionPtr + 1], a + ld a, l + ld [wTilesetCollisionPtr], a + xor a + ld [wGrassRate], a + inc a ; LINK_STATE_IN_CABLE_CLUB + ld [wLinkState], a + ld [hJoy5], a + ld a, 10 + ld [wAudioFadeOutControl], a + ld a, BANK(Music_Celadon) + ld [wAudioSavedROMBank], a + ld a, MUSIC_CELADON + ld [wNewSoundID], a + jp PlaySound + +EmptyFunc3: + ret + +Diploma_TextBoxBorder: + call GetPredefRegisters + +; b = height +; c = width +CableClub_TextBoxBorder: + push hl + ld a, $78 ; border upper left corner tile + ld [hli], a + inc a ; border top horizontal line tile + call CableClub_DrawHorizontalLine + inc a ; border upper right corner tile + ld [hl], a + pop hl + ld de, 20 + add hl, de +.loop + push hl + ld a, $7b ; border left vertical line tile + ld [hli], a + ld a, " " + call CableClub_DrawHorizontalLine + ld [hl], $77 ; border right vertical line tile + pop hl + ld de, 20 + add hl, de + dec b + jr nz, .loop + ld a, $7c ; border lower left corner tile + ld [hli], a + ld a, $76 ; border bottom horizontal line tile + call CableClub_DrawHorizontalLine + ld [hl], $7d ; border lower right corner tile + ret + +; c = width +CableClub_DrawHorizontalLine: + ld d, c +.loop + ld [hli], a + dec d + jr nz, .loop + ret + +LoadTrainerInfoTextBoxTiles: + ld de, TrainerInfoTextBoxTileGraphics + ld hl, vChars2 + $760 + lb bc, BANK(TrainerInfoTextBoxTileGraphics), (TrainerInfoTextBoxTileGraphicsEnd - TrainerInfoTextBoxTileGraphics) / $10 + jp CopyVideoData diff --git a/de/engine/clear_save.asm b/de/engine/clear_save.asm new file mode 100755 index 00000000..c37ee4a7 --- /dev/null +++ b/de/engine/clear_save.asm @@ -0,0 +1,23 @@ +DoClearSaveDialogue: + call ClearScreen + call RunDefaultPaletteCommand + call LoadFontTilePatterns + call LoadTextBoxTilePatterns + ld hl, ClearSaveDataText + call PrintText + coord hl, 13, 7 + lb bc, 8, 14 + ld a, NO_YES_MENU + ld [wTwoOptionMenuID], a + ld a, TWO_OPTION_MENU + ld [wTextBoxID], a + call DisplayTextBoxID + ld a, [wCurrentMenuItem] + and a + jp z, Init + callba ClearSAV + jp Init + +ClearSaveDataText: + TX_FAR _ClearSaveDataText + db "@" diff --git a/de/engine/evolve_trade.asm b/de/engine/evolve_trade.asm new file mode 100755 index 00000000..d3a0ee63 --- /dev/null +++ b/de/engine/evolve_trade.asm @@ -0,0 +1,45 @@ +EvolveTradeMon: +; Verify the TradeMon's species name before +; attempting to initiate a trade evolution. + +; The names of the trade evolutions in Blue (JP) +; are checked. In that version, TradeMons that +; can evolve are Graveler and Haunter. + +; In localization, this check was translated +; before monster names were finalized. +; Then, Haunter's name was "Spectre". +; Since its name no longer starts with +; "SP", it is prevented from evolving. + +; This may have been why Red/Green's trades +; were used instead, where none can evolve. + +; This was fixed in Yellow. + + ;ld a, [wInGameTradeReceiveMonName] + + ; GRAVELER + ;cp "G" + ;jr z, .ok + + ; "SPECTRE" (HAUNTER) + ;cp "S" + ;ret nz + ;ld a, [wInGameTradeReceiveMonName + 1] + ;cp "P" + ;ret nz + ret + +.ok + ld a, [wPartyCount] + dec a + ld [wWhichPokemon], a + ld a, $1 + ld [wForceEvolution], a + ld a, LINK_STATE_TRADING + ld [wLinkState], a + callab TryEvolvingMon + xor a ; LINK_STATE_NONE + ld [wLinkState], a + jp PlayDefaultMusic diff --git a/de/engine/hall_of_fame.asm b/de/engine/hall_of_fame.asm new file mode 100755 index 00000000..7380362a --- /dev/null +++ b/de/engine/hall_of_fame.asm @@ -0,0 +1,288 @@ +AnimateHallOfFame: + call HoFFadeOutScreenAndMusic + call ClearScreen + ld c, 100 + call DelayFrames + call LoadFontTilePatterns + call LoadTextBoxTilePatterns + call DisableLCD + ld hl,vBGMap0 + ld bc, $800 + ld a, " " + call FillMemory + call EnableLCD + ld hl, rLCDC + set 3, [hl] + xor a + ld hl, wHallOfFame + ld bc, HOF_TEAM + call FillMemory + xor a + ld [wUpdateSpritesEnabled], a + ld [hTilesetType], a + ld [wSpriteFlipped], a + ld [wLetterPrintingDelayFlags], a ; no delay + ld [wHoFMonOrPlayer], a ; mon + inc a + ld [H_AUTOBGTRANSFERENABLED], a + ld hl, wNumHoFTeams + ld a, [hl] + inc a + jr z, .skipInc ; don't wrap around to 0 + inc [hl] +.skipInc + ld a, $90 + ld [hWY], a + ld c, BANK(Music_HallOfFame) + ld a, MUSIC_HALL_OF_FAME + call PlayMusic + ld hl, wPartySpecies + ld c, $ff +.partyMonLoop + ld a, [hli] + cp $ff + jr z, .doneShowingParty + inc c + push hl + push bc + ld [wHoFMonSpecies], a + ld a, c + ld [wHoFPartyMonIndex], a + ld hl, wPartyMon1Level + ld bc, wPartyMon2 - wPartyMon1 + call AddNTimes + ld a, [hl] + ld [wHoFMonLevel], a + call HoFShowMonOrPlayer + call HoFDisplayAndRecordMonInfo + ld c, 80 + call DelayFrames + coord hl, 2, 13 + ld b, 3 + ld c, 14 + call TextBoxBorder + coord hl, 4, 15 + ld de, HallOfFameText + call PlaceString + ld c, 180 + call DelayFrames + call GBFadeOutToWhite + pop bc + pop hl + jr .partyMonLoop +.doneShowingParty + ld a, c + inc a + ld hl, wHallOfFame + ld bc, HOF_MON + call AddNTimes + ld [hl], $ff + call SaveHallOfFameTeams + xor a + ld [wHoFMonSpecies], a + inc a + ld [wHoFMonOrPlayer], a ; player + call HoFShowMonOrPlayer + call HoFDisplayPlayerStats + call HoFFadeOutScreenAndMusic + xor a + ld [hWY], a + ld hl, rLCDC + res 3, [hl] + ret + +HallOfFameText: + db "RUHMESHALLE@" + +HoFShowMonOrPlayer: + call ClearScreen + ld a, $d0 + ld [hSCY], a + ld a, $c0 + ld [hSCX], a + ld a, [wHoFMonSpecies] + ld [wcf91], a + ld [wd0b5], a + ld [wBattleMonSpecies2], a + ld [wWholeScreenPaletteMonSpecies], a + ld a, [wHoFMonOrPlayer] + and a + jr z, .showMon +; show player + call HoFLoadPlayerPics + jr .next1 +.showMon + coord hl, 12, 5 + call GetMonHeader + call LoadFrontSpriteByMonIndex + predef LoadMonBackPic +.next1 + ld b, SET_PAL_POKEMON_WHOLE_SCREEN + ld c, 0 + call RunPaletteCommand + ld a, %11100100 + ld [rBGP], a + ld c, $31 ; back pic + call HoFLoadMonPlayerPicTileIDs + ld d, $a0 + ld e, 4 + ld a, [wOnSGB] + and a + jr z, .next2 + sla e ; scroll more slowly on SGB +.next2 + call .ScrollPic ; scroll back pic left + xor a + ld [hSCY], a + ld c, a ; front pic + call HoFLoadMonPlayerPicTileIDs + ld d, 0 + ld e, -4 +; scroll front pic right + +.ScrollPic + call DelayFrame + ld a, [hSCX] + add e + ld [hSCX], a + cp d + jr nz, .ScrollPic + ret + +HoFDisplayAndRecordMonInfo: + ld a, [wHoFPartyMonIndex] + ld hl, wPartyMonNicks + call GetPartyMonName + call HoFDisplayMonInfo + jp HoFRecordMonInfo + +HoFDisplayMonInfo: + coord hl, 0, 2 + ld b, 9 + ld c, 10 + call TextBoxBorder + coord hl, 2, 6 + ld de, HoFMonInfoText + call PlaceString + coord hl, 1, 4 + ld de, wcd6d + call PlaceString + ld a, [wHoFMonLevel] + coord hl, 8, 7 + call PrintLevelCommon + ld a, [wHoFMonSpecies] + ld [wd0b5], a + coord hl, 3, 9 + predef PrintMonType + ld a, [wHoFMonSpecies] + jp PlayCry + +HoFMonInfoText: + db "LEVEL/" + next "TYP1/" + next "TYP2/@" + +HoFLoadPlayerPics: + ld de, RedPicFront + ld a, BANK(RedPicFront) + call UncompressSpriteFromDE + ld hl, sSpriteBuffer1 + ld de, sSpriteBuffer0 + ld bc, $310 + call CopyData + ld de, vFrontPic + call InterlaceMergeSpriteBuffers + ld de, RedPicBack + ld a, BANK(RedPicBack) + call UncompressSpriteFromDE + predef ScaleSpriteByTwo + ld de, vBackPic + call InterlaceMergeSpriteBuffers + ld c, $1 + +HoFLoadMonPlayerPicTileIDs: +; c = base tile ID + ld b, 0 + coord hl, 12, 5 + predef_jump CopyTileIDsFromList + +HoFDisplayPlayerStats: + SetEvent EVENT_HALL_OF_FAME_DEX_RATING + predef DisplayDexRating + coord hl, 0, 4 + ld b, 6 + ld c, 10 + call TextBoxBorder + coord hl, 5, 0 + ld b, 2 + ld c, 9 + call TextBoxBorder + coord hl, 7, 2 + ld de, wPlayerName + call PlaceString + coord hl, 1, 6 + ld de, HoFPlayTimeText + call PlaceString + coord hl, 5, 7 + ld de, wPlayTimeHours + lb bc, 1, 3 + call PrintNumber + ld [hl], $6d + inc hl + ld de, wPlayTimeMinutes + lb bc, LEADING_ZEROES | 1, 2 + call PrintNumber + coord hl, 1, 9 + ld de, HoFMoneyText + call PlaceString + coord hl, 4, 10 + ld de, wPlayerMoney + ld c, $a3 + call PrintBCDNumber + ld hl, DexSeenOwnedText + call HoFPrintTextAndDelay + ld hl, DexRatingText + call HoFPrintTextAndDelay + ld hl, wDexRatingText + +HoFPrintTextAndDelay: + call PrintText + ld c, 120 + jp DelayFrames + +HoFPlayTimeText: + db "SPIELZEIT@" + +HoFMoneyText: + db "GELD@" + +DexSeenOwnedText: + TX_FAR _DexSeenOwnedText + db "@" + +DexRatingText: + TX_FAR _DexRatingText + db "@" + +HoFRecordMonInfo: + ld hl, wHallOfFame + ld bc, HOF_MON + ld a, [wHoFPartyMonIndex] + call AddNTimes + ld a, [wHoFMonSpecies] + ld [hli], a + ld a, [wHoFMonLevel] + ld [hli], a + ld e, l + ld d, h + ld hl, wcd6d + ld bc, NAME_LENGTH + jp CopyData + +HoFFadeOutScreenAndMusic: + ld a, 10 + ld [wAudioFadeOutCounterReloadValue], a + ld [wAudioFadeOutCounter], a + ld a, $ff + ld [wAudioFadeOutControl], a + jp GBFadeOutToWhite diff --git a/de/engine/hidden_object_functions17.asm b/de/engine/hidden_object_functions17.asm new file mode 100755 index 00000000..bc490e3c --- /dev/null +++ b/de/engine/hidden_object_functions17.asm @@ -0,0 +1,475 @@ +PrintRedSNESText: + call EnableAutoTextBoxDrawing + tx_pre_jump RedBedroomSNESText + +RedBedroomSNESText: + TX_FAR _RedBedroomSNESText + db "@" + +OpenRedsPC: + call EnableAutoTextBoxDrawing + tx_pre_jump RedBedroomPCText + +RedBedroomPCText: + TX_PLAYERS_PC + +Route15GateLeftBinoculars: + ld a, [wSpriteStateData1 + 9] + cp SPRITE_FACING_UP + ret nz + call EnableAutoTextBoxDrawing + tx_pre Route15UpstairsBinocularsText + ld a, ARTICUNO + ld [wcf91], a + call PlayCry + jp DisplayMonFrontSpriteInBox + +Route15UpstairsBinocularsText: + TX_FAR _Route15UpstairsBinocularsText + db "@" + +AerodactylFossil: + ld a, FOSSIL_AERODACTYL + ld [wcf91], a + call DisplayMonFrontSpriteInBox + call EnableAutoTextBoxDrawing + tx_pre AerodactylFossilText + ret + +AerodactylFossilText: + TX_FAR _AerodactylFossilText + db "@" + +KabutopsFossil: + ld a, FOSSIL_KABUTOPS + ld [wcf91], a + call DisplayMonFrontSpriteInBox + call EnableAutoTextBoxDrawing + tx_pre KabutopsFossilText + ret + +KabutopsFossilText: + TX_FAR _KabutopsFossilText + db "@" + +DisplayMonFrontSpriteInBox: +; Displays a pokemon's front sprite in a pop-up window. +; [wcf91] = pokemon internal id number + ld a, 1 + ld [H_AUTOBGTRANSFERENABLED], a + call Delay3 + xor a + ld [hWY], a + call SaveScreenTilesToBuffer1 + ld a, MON_SPRITE_POPUP + ld [wTextBoxID], a + call DisplayTextBoxID + call UpdateSprites + ld a, [wcf91] + ld [wd0b5], a + call GetMonHeader + ld de, vChars1 + $310 + call LoadMonFrontSprite + ld a, $80 + ld [hStartTileID], a + coord hl, 10, 11 + predef AnimateSendingOutMon + call WaitForTextScrollButtonPress + call LoadScreenTilesFromBuffer1 + call Delay3 + ld a, $90 + ld [hWY], a + ret + +PrintBlackboardLinkCableText: + call EnableAutoTextBoxDrawing + ld a, $1 + ld [wDoNotWaitForButtonPressAfterDisplayingText], a + ld a, [wHiddenObjectFunctionArgument] + call PrintPredefTextID + ret + +LinkCableHelp: + TX_ASM + call SaveScreenTilesToBuffer1 + ld hl, LinkCableHelpText1 + call PrintText + xor a + ld [wMenuItemOffset], a ; not used + ld [wCurrentMenuItem], a + ld [wLastMenuItem], a + ld a, A_BUTTON | B_BUTTON + ld [wMenuWatchedKeys], a + ld a, 3 + ld [wMaxMenuItem], a + ld a, 2 + ld [wTopMenuItemY], a + ld a, 1 + ld [wTopMenuItemX], a +.linkHelpLoop + ld hl, wd730 + set 6, [hl] + coord hl, 0, 0 + ld b, 8 + ld c, 14 + call TextBoxBorder + coord hl, 2, 2 + ld de, HowToLinkText + call PlaceString + ld hl, LinkCableHelpText2 + call PrintText + call HandleMenuInput + bit 1, a ; pressed b + jr nz, .exit + ld a, [wCurrentMenuItem] + cp 3 ; pressed a on "STOP READING" + jr z, .exit + ld hl, wd730 + res 6, [hl] + ld hl, LinkCableInfoTexts + add a + ld d, 0 + ld e, a + add hl, de + ld a, [hli] + ld h, [hl] + ld l, a + call PrintText + jp .linkHelpLoop +.exit + ld hl, wd730 + res 6, [hl] + call LoadScreenTilesFromBuffer1 + jp TextScriptEnd + +LinkCableHelpText1: + TX_FAR _LinkCableHelpText1 + db "@" + +LinkCableHelpText2: + TX_FAR _LinkCableHelpText2 + db "@" + +HowToLinkText: + db "LINK-INFO" + next "KOLOSSEUM" + next "HANDELSCENTER" + next "VERLASSEN@" + +LinkCableInfoTexts: + dw LinkCableInfoText1 + dw LinkCableInfoText2 + dw LinkCableInfoText3 + +LinkCableInfoText1: + TX_FAR _LinkCableInfoText1 + db "@" + +LinkCableInfoText2: + TX_FAR _LinkCableInfoText2 + db "@" + +LinkCableInfoText3: + TX_FAR _LinkCableInfoText3 + db "@" + +ViridianSchoolBlackboard: + TX_ASM + call SaveScreenTilesToBuffer1 + ld hl, ViridianSchoolBlackboardText1 + call PrintText + xor a + ld [wMenuItemOffset], a + ld [wCurrentMenuItem], a + ld [wLastMenuItem], a + ld a, D_LEFT | D_RIGHT | A_BUTTON | B_BUTTON + ld [wMenuWatchedKeys], a + ld a, 2 + ld [wMaxMenuItem], a + ld a, 2 + ld [wTopMenuItemY], a + ld a, 1 + ld [wTopMenuItemX], a +.blackboardLoop + ld hl, wd730 + set 6, [hl] + coord hl, 0, 0 + lb bc, 6, 10 + call TextBoxBorder + coord hl, 1, 2 + ld de, StatusAilmentText1 + call PlaceString + coord hl, 6, 2 + ld de, StatusAilmentText2 + call PlaceString + ld hl, ViridianSchoolBlackboardText2 + call PrintText + call HandleMenuInput ; pressing up and down is handled in here + bit 1, a ; pressed b + jr nz, .exitBlackboard + bit 4, a ; pressed right + jr z, .didNotPressRight + ; move cursor to right column + ld a, 2 + ld [wMaxMenuItem], a + ld a, 2 + ld [wTopMenuItemY], a + ld a, 6 + ld [wTopMenuItemX], a + ld a, 3 ; in the the right column, use an offset to prevent overlap + ld [wMenuItemOffset], a + jr .blackboardLoop +.didNotPressRight + bit 5, a ; pressed left + jr z, .didNotPressLeftOrRight + ; move cursor to left column + ld a, 2 + ld [wMaxMenuItem], a + ld a, 2 + ld [wTopMenuItemY], a + ld a, 1 + ld [wTopMenuItemX], a + xor a + ld [wMenuItemOffset], a + jr .blackboardLoop +.didNotPressLeftOrRight + ld a, [wCurrentMenuItem] + ld b, a + ld a, [wMenuItemOffset] + add b + cp 5 ; cursor is pointing to "QUIT" + jr z, .exitBlackboard + ; we must have pressed a on a status condition + ; so print the text + ld hl, wd730 + res 6, [hl] + ld hl, ViridianBlackboardStatusPointers + add a + ld d, 0 + ld e, a + add hl, de + ld a, [hli] + ld h, [hl] + ld l, a + call PrintText + jp .blackboardLoop +.exitBlackboard + ld hl, wd730 + res 6, [hl] + call LoadScreenTilesFromBuffer1 + jp TextScriptEnd + +ViridianSchoolBlackboardText1: + TX_FAR _ViridianSchoolBlackboardText1 + db "@" + +ViridianSchoolBlackboardText2: + TX_FAR _ViridianSchoolBlackboardText2 + db "@" + +StatusAilmentText1: + db " SLF" + next " GIF" + next " PAR@" + +StatusAilmentText2: + db " BRT" + next " GFR" + next " ZUR.@" + +ViridianBlackboardStatusPointers: + dw ViridianBlackboardSleepText + dw ViridianBlackboardPoisonText + dw ViridianBlackboardPrlzText + dw ViridianBlackboardBurnText + dw ViridianBlackboardFrozenText + +ViridianBlackboardSleepText: + TX_FAR _ViridianBlackboardSleepText + db "@" + +ViridianBlackboardPoisonText: + TX_FAR _ViridianBlackboardPoisonText + db "@" + +ViridianBlackboardPrlzText: + TX_FAR _ViridianBlackboardPrlzText + db "@" + +ViridianBlackboardBurnText: + TX_FAR _ViridianBlackboardBurnText + db "@" + +ViridianBlackboardFrozenText: + TX_FAR _ViridianBlackboardFrozenText + db "@" + +PrintTrashText: + call EnableAutoTextBoxDrawing + tx_pre_jump VermilionGymTrashText + +VermilionGymTrashText: + TX_FAR _VermilionGymTrashText + db "@" + +GymTrashScript: + call EnableAutoTextBoxDrawing + ld a, [wHiddenObjectFunctionArgument] + ld [wGymTrashCanIndex], a + +; Don't do the trash can puzzle if it's already been done. + CheckEvent EVENT_2ND_LOCK_OPENED + jr z, .ok + + tx_pre_jump VermilionGymTrashText + +.ok + CheckEventReuseA EVENT_1ST_LOCK_OPENED + jr nz, .trySecondLock + + ld a, [wFirstLockTrashCanIndex] + ld b, a + ld a, [wGymTrashCanIndex] + cp b + jr z, .openFirstLock + + tx_pre_id VermilionGymTrashText + jr .done + +.openFirstLock +; Next can is trying for the second switch. + SetEvent EVENT_1ST_LOCK_OPENED + + ld hl, GymTrashCans + ld a, [wGymTrashCanIndex] + ; * 5 + ld b, a + add a + add a + add b + + ld d, 0 + ld e, a + add hl, de + ld a, [hli] + +; There is a bug in this code. It should calculate a value in the range [0, 3] +; but if the mask and random number don't have any 1 bits in common, then +; the result of the AND will be 0. When 1 is subtracted from that, the value +; will become $ff. This will result in 255 being added to hl, which will cause +; hl to point to one of the zero bytes that pad the end of the ROM bank. +; Trash can 0 was intended to be able to have the second lock only when the +; first lock was in trash can 1 or 3. However, due to this bug, trash can 0 can +; have the second lock regardless of which trash can had the first lock. + + ld [hGymTrashCanRandNumMask], a + push hl + call Random + swap a + ld b, a + ld a, [hGymTrashCanRandNumMask] + and b + dec a + pop hl + + ld d, 0 + ld e, a + add hl, de + ld a, [hl] + and $f + ld [wSecondLockTrashCanIndex], a + + tx_pre_id VermilionGymTrashSuccessText1 + jr .done + +.trySecondLock + ld a, [wSecondLockTrashCanIndex] + ld b, a + ld a, [wGymTrashCanIndex] + cp b + jr z, .openSecondLock + +; Reset the cans. + ResetEvent EVENT_1ST_LOCK_OPENED + call Random + + and $e + ld [wFirstLockTrashCanIndex], a + + tx_pre_id VermilionGymTrashFailText + jr .done + +.openSecondLock +; Completed the trash can puzzle. + SetEvent EVENT_2ND_LOCK_OPENED + ld hl, wCurrentMapScriptFlags + set 6, [hl] + + tx_pre_id VermilionGymTrashSuccessText3 + +.done + jp PrintPredefTextID + +GymTrashCans: +; byte 0: mask for random number +; bytes 1-4: indices of the trash cans that can have the second lock +; (but see the comment above explaining a bug regarding this) +; Note that the mask is simply the number of valid trash can indices that +; follow. The remaining bytes are filled with 0 to pad the length of each entry +; to 5 bytes. + db 2, 1, 3, 0, 0 ; 0 + db 3, 0, 2, 4, 0 ; 1 + db 2, 1, 5, 0, 0 ; 2 + db 3, 0, 4, 6, 0 ; 3 + db 4, 1, 3, 5, 7 ; 4 + db 3, 2, 4, 8, 0 ; 5 + db 3, 3, 7, 9, 0 ; 6 + db 4, 4, 6, 8, 10 ; 7 + db 3, 5, 7, 11, 0 ; 8 + db 3, 6, 10, 12, 0 ; 9 + db 4, 7, 9, 11, 13 ; 10 + db 3, 8, 10, 14, 0 ; 11 + db 2, 9, 13, 0, 0 ; 12 + db 3, 10, 12, 14, 0 ; 13 + db 2, 11, 13, 0, 0 ; 14 + +VermilionGymTrashSuccessText1: + TX_FAR _VermilionGymTrashSuccessText1 + TX_ASM + call WaitForSoundToFinish + ld a, SFX_SWITCH + call PlaySound + call WaitForSoundToFinish + jp TextScriptEnd + +; unused +VermilionGymTrashSuccessText2: + TX_FAR _VermilionGymTrashSuccessText2 + db "@" + +; unused +VermilionGymTrashSuccesPlaySfx: + TX_ASM + call WaitForSoundToFinish + ld a, SFX_SWITCH + call PlaySound + call WaitForSoundToFinish + jp TextScriptEnd + +VermilionGymTrashSuccessText3: + TX_FAR _VermilionGymTrashSuccessText3 + TX_ASM + call WaitForSoundToFinish + ld a, SFX_GO_INSIDE + call PlaySound + call WaitForSoundToFinish + jp TextScriptEnd + +VermilionGymTrashFailText: + TX_FAR _VermilionGymTrashFailText + TX_ASM + call WaitForSoundToFinish + ld a, SFX_DENIED + call PlaySound + call WaitForSoundToFinish + jp TextScriptEnd diff --git a/de/engine/hidden_object_functions7.asm b/de/engine/hidden_object_functions7.asm new file mode 100755 index 00000000..39d72e2e --- /dev/null +++ b/de/engine/hidden_object_functions7.asm @@ -0,0 +1,467 @@ +PrintNewBikeText: + call EnableAutoTextBoxDrawing + tx_pre_jump NewBicycleText + +NewBicycleText: + TX_FAR _NewBicycleText + db "@" + +DisplayOakLabLeftPoster: + call EnableAutoTextBoxDrawing + tx_pre_jump PushStartText + +PushStartText: + TX_FAR _PushStartText + db "@" + +DisplayOakLabRightPoster: + call EnableAutoTextBoxDrawing + ld hl, wPokedexOwned + ld b, wPokedexOwnedEnd - wPokedexOwned + call CountSetBits + ld a, [wNumSetBits] + cp 2 + tx_pre_id SaveOptionText + jr c, .ownLessThanTwo + ; own two or more mon + tx_pre_id StrengthsAndWeaknessesText +.ownLessThanTwo + jp PrintPredefTextID + +SaveOptionText: + TX_FAR _SaveOptionText + db "@" + +StrengthsAndWeaknessesText: + TX_FAR _StrengthsAndWeaknessesText + db "@" + +SafariZoneCheck: + CheckEventHL EVENT_IN_SAFARI_ZONE ; if we are not in the Safari Zone, + jr z, SafariZoneGameStillGoing ; don't bother printing game over text + ld a, [wNumSafariBalls] + and a + jr z, SafariZoneGameOver + jr SafariZoneGameStillGoing + +SafariZoneCheckSteps: + ld a, [wSafariSteps] + ld b, a + ld a, [wSafariSteps + 1] + ld c, a + or b + jr z, SafariZoneGameOver + dec bc + ld a, b + ld [wSafariSteps], a + ld a, c + ld [wSafariSteps + 1], a +SafariZoneGameStillGoing: + xor a + ld [wSafariZoneGameOver], a + ret + +SafariZoneGameOver: + call EnableAutoTextBoxDrawing + xor a + ld [wAudioFadeOutControl], a + dec a + call PlaySound + ld c, BANK(SFX_Safari_Zone_PA) + ld a, SFX_SAFARI_ZONE_PA + call PlayMusic +.waitForMusicToPlay + ld a, [wChannelSoundIDs + Ch4] + cp SFX_SAFARI_ZONE_PA + jr nz, .waitForMusicToPlay + ld a, TEXT_SAFARI_GAME_OVER + ld [hSpriteIndexOrTextID], a + call DisplayTextID + xor a + ld [wPlayerMovingDirection], a + ld a, SAFARI_ZONE_ENTRANCE + ld [hWarpDestinationMap], a + ld a, $3 + ld [wDestinationWarpID], a + ld a, $5 + ld [wSafariZoneEntranceCurScript], a + SetEvent EVENT_SAFARI_GAME_OVER + ld a, 1 + ld [wSafariZoneGameOver], a + ret + +PrintSafariGameOverText: + xor a + ld [wJoyIgnore], a + ld hl, SafariGameOverText + jp PrintText + +SafariGameOverText: + TX_ASM + ld a, [wNumSafariBalls] + and a + jr z, .noMoreSafariBalls + ld hl, TimesUpText + call PrintText +.noMoreSafariBalls + ld hl, GameOverText + call PrintText + jp TextScriptEnd + +TimesUpText: + TX_FAR _TimesUpText + db "@" + +GameOverText: + TX_FAR _GameOverText + db "@" + +PrintCinnabarQuiz: + ld a, [wSpriteStateData1 + 9] + cp SPRITE_FACING_UP + ret nz + call EnableAutoTextBoxDrawing + tx_pre_jump CinnabarGymQuiz + +CinnabarGymQuiz: + TX_ASM + xor a + ld [wOpponentAfterWrongAnswer], a + ld a, [wHiddenObjectFunctionArgument] + push af + and $f + ld [hGymGateIndex], a + pop af + and $f0 + swap a + ld [$ffdc], a + ld hl, CinnabarGymQuizIntroText + call PrintText + ld a, [hGymGateIndex] + dec a + add a + ld d, 0 + ld e, a + ld hl, CinnabarQuizQuestions + add hl, de + ld a, [hli] + ld h, [hl] + ld l, a + call PrintText + ld a, 1 + ld [wDoNotWaitForButtonPressAfterDisplayingText], a + call CinnabarGymQuiz_1ea92 + jp TextScriptEnd + +CinnabarGymQuizIntroText: + TX_FAR _CinnabarGymQuizIntroText + db "@" + +CinnabarQuizQuestions: + dw CinnabarQuizQuestionsText1 + dw CinnabarQuizQuestionsText2 + dw CinnabarQuizQuestionsText3 + dw CinnabarQuizQuestionsText4 + dw CinnabarQuizQuestionsText5 + dw CinnabarQuizQuestionsText6 + +CinnabarQuizQuestionsText1: + TX_FAR _CinnabarQuizQuestionsText1 + db "@" + +CinnabarQuizQuestionsText2: + TX_FAR _CinnabarQuizQuestionsText2 + db "@" + +CinnabarQuizQuestionsText3: + TX_FAR _CinnabarQuizQuestionsText3 + db "@" + +CinnabarQuizQuestionsText4: + TX_FAR _CinnabarQuizQuestionsText4 + db "@" + +CinnabarQuizQuestionsText5: + TX_FAR _CinnabarQuizQuestionsText5 + db "@" + +CinnabarQuizQuestionsText6: + TX_FAR _CinnabarQuizQuestionsText6 + db "@" + +CinnabarGymGateFlagAction: + EventFlagAddress hl, EVENT_CINNABAR_GYM_GATE0_UNLOCKED + predef_jump FlagActionPredef + +CinnabarGymQuiz_1ea92: + call YesNoChoice + ld a, [$ffdc] + ld c, a + ld a, [wCurrentMenuItem] + cp c + jr nz, .wrongAnswer + ld hl, wCurrentMapScriptFlags + set 5, [hl] + ld a, [hGymGateIndex] + ld [$ffe0], a + ld hl, CinnabarGymQuizCorrectText + call PrintText + ld a, [$ffe0] + AdjustEventBit EVENT_CINNABAR_GYM_GATE0_UNLOCKED, 0 + ld c, a + ld b, FLAG_SET + call CinnabarGymGateFlagAction + jp UpdateCinnabarGymGateTileBlocks_ +.wrongAnswer + call WaitForSoundToFinish + ld a, SFX_DENIED + call PlaySound + call WaitForSoundToFinish + ld hl, CinnabarGymQuizIncorrectText + call PrintText + ld a, [hGymGateIndex] + add $2 + AdjustEventBit EVENT_BEAT_CINNABAR_GYM_TRAINER_0, 2 + ld c, a + ld b, FLAG_TEST + EventFlagAddress hl, EVENT_BEAT_CINNABAR_GYM_TRAINER_0 + predef FlagActionPredef + ld a, c + and a + ret nz + ld a, [hGymGateIndex] + add $2 + ld [wOpponentAfterWrongAnswer], a + ret + +CinnabarGymQuizCorrectText: + TX_SFX_ITEM_1 + TX_FAR _CinnabarGymQuizCorrectText + TX_BLINK + TX_ASM + + ld a, [$ffe0] + AdjustEventBit EVENT_CINNABAR_GYM_GATE0_UNLOCKED, 0 + ld c, a + ld b, FLAG_TEST + call CinnabarGymGateFlagAction + ld a, c + and a + jp nz, TextScriptEnd + call WaitForSoundToFinish + ld a, SFX_GO_INSIDE + call PlaySound + call WaitForSoundToFinish + jp TextScriptEnd + +CinnabarGymQuizIncorrectText: + TX_FAR _CinnabarGymQuizIncorrectText + db "@" + +UpdateCinnabarGymGateTileBlocks_: +; Update the overworld map with open floor blocks or locked gate blocks +; depending on event flags. + ld a, 6 + ld [hGymGateIndex], a +.loop + ld a, [hGymGateIndex] + dec a + add a + add a + ld d, 0 + ld e, a + ld hl, CinnabarGymGateCoords + add hl, de + ld a, [hli] + ld b, [hl] + ld c, a + inc hl + ld a, [hl] + ld [wGymGateTileBlock], a + push bc + ld a, [hGymGateIndex] + ld [$ffe0], a + AdjustEventBit EVENT_CINNABAR_GYM_GATE0_UNLOCKED, 0 + ld c, a + ld b, FLAG_TEST + call CinnabarGymGateFlagAction + ld a, c + and a + jr nz, .unlocked + ld a, [wGymGateTileBlock] + jr .next +.unlocked + ld a, $e +.next + pop bc + ld [wNewTileBlockID], a + predef ReplaceTileBlock + ld hl, hGymGateIndex + dec [hl] + jr nz, .loop + ret + +CinnabarGymGateCoords: + ; format: x-coord, y-coord, direction, padding + ; direction: $54 = horizontal gate, $5f = vertical gate + db $09,$03,$54,$00 + db $06,$03,$54,$00 + db $06,$06,$54,$00 + db $03,$08,$5f,$00 + db $02,$06,$54,$00 + db $02,$03,$54,$00 + +PrintMagazinesText: + call EnableAutoTextBoxDrawing + tx_pre MagazinesText + ret + +MagazinesText: + TX_FAR _MagazinesText + db "@" + +BillsHousePC: + call EnableAutoTextBoxDrawing + ld a, [wSpriteStateData1 + 9] + cp SPRITE_FACING_UP + ret nz + CheckEvent EVENT_LEFT_BILLS_HOUSE_AFTER_HELPING + jr nz, .displayBillsHousePokemonList + CheckEventReuseA EVENT_USED_CELL_SEPARATOR_ON_BILL + jr nz, .displayBillsHouseMonitorText + CheckEventReuseA EVENT_BILL_SAID_USE_CELL_SEPARATOR + jr nz, .doCellSeparator +.displayBillsHouseMonitorText + tx_pre_jump BillsHouseMonitorText +.doCellSeparator + ld a, $1 + ld [wDoNotWaitForButtonPressAfterDisplayingText], a + tx_pre BillsHouseInitiatedText + ld c, 32 + call DelayFrames + ld a, SFX_TINK + call PlaySound + call WaitForSoundToFinish + ld c, 80 + call DelayFrames + ld a, SFX_SHRINK + call PlaySound + call WaitForSoundToFinish + ld c, 48 + call DelayFrames + ld a, SFX_TINK + call PlaySound + call WaitForSoundToFinish + ld c, 32 + call DelayFrames + ld a, SFX_GET_ITEM_1 + call PlaySound + call WaitForSoundToFinish + call PlayDefaultMusic + SetEvent EVENT_USED_CELL_SEPARATOR_ON_BILL + ret +.displayBillsHousePokemonList + ld a, $1 + ld [wDoNotWaitForButtonPressAfterDisplayingText], a + tx_pre BillsHousePokemonList + ret + +BillsHouseMonitorText: + TX_FAR _BillsHouseMonitorText + db "@" + +BillsHouseInitiatedText: + TX_FAR _BillsHouseInitiatedText + TX_BLINK + TX_ASM + ld a, $ff + ld [wNewSoundID], a + call PlaySound + ld c, 16 + call DelayFrames + ld a, SFX_SWITCH + call PlaySound + call WaitForSoundToFinish + ld c, 60 + call DelayFrames + jp TextScriptEnd + +BillsHousePokemonList: + TX_ASM + call SaveScreenTilesToBuffer1 + ld hl, BillsHousePokemonListText1 + call PrintText + xor a + ld [wMenuItemOffset], a ; not used + ld [wCurrentMenuItem], a + ld [wLastMenuItem], a + ld a, A_BUTTON | B_BUTTON + ld [wMenuWatchedKeys], a + ld a, 4 + ld [wMaxMenuItem], a + ld a, 2 + ld [wTopMenuItemY], a + ld a, 1 + ld [wTopMenuItemX], a +.billsPokemonLoop + ld hl, wd730 + set 6, [hl] + coord hl, 0, 0 + ld b, 10 + ld c, 9 + call TextBoxBorder + coord hl, 2, 2 + ld de, BillsMonListText + call PlaceString + ld hl, BillsHousePokemonListText2 + call PrintText + call SaveScreenTilesToBuffer2 + call HandleMenuInput + bit 1, a ; pressed b + jr nz, .cancel + ld a, [wCurrentMenuItem] + add EEVEE + cp EEVEE + jr z, .displayPokedex + cp FLAREON + jr z, .displayPokedex + cp JOLTEON + jr z, .displayPokedex + cp VAPOREON + jr z, .displayPokedex + jr .cancel +.displayPokedex + call DisplayPokedex + call LoadScreenTilesFromBuffer2 + jr .billsPokemonLoop +.cancel + ld hl, wd730 + res 6, [hl] + call LoadScreenTilesFromBuffer2 + jp TextScriptEnd + +BillsHousePokemonListText1: + TX_FAR _BillsHousePokemonListText1 + db "@" + +BillsMonListText: + db "EVOLI" + next "FLAMARA" + next "BLITZA" + next "AQUANA" + next "ZURÜCK@" + +BillsHousePokemonListText2: + TX_FAR _BillsHousePokemonListText2 + db "@" + +DisplayOakLabEmailText: + ld a, [wSpriteStateData1 + 9] + cp SPRITE_FACING_UP + ret nz + call EnableAutoTextBoxDrawing + tx_pre_jump OakLabEmailText + +OakLabEmailText: + TX_FAR _OakLabEmailText + db "@" diff --git a/de/engine/items/items.asm b/de/engine/items/items.asm new file mode 100755 index 00000000..6010d83e --- /dev/null +++ b/de/engine/items/items.asm @@ -0,0 +1,2989 @@ +UseItem_: + ld a,1 + ld [wActionResultOrTookBattleTurn],a ; initialise to success value + ld a,[wcf91] ;contains item_ID + cp a,HM_01 + jp nc,ItemUseTMHM + ld hl,ItemUsePtrTable + dec a + add a + ld c,a + ld b,0 + add hl,bc + ld a,[hli] + ld h,[hl] + ld l,a + jp hl + +ItemUsePtrTable: + dw ItemUseBall ; MASTER_BALL + dw ItemUseBall ; ULTRA_BALL + dw ItemUseBall ; GREAT_BALL + dw ItemUseBall ; POKE_BALL + dw ItemUseTownMap ; TOWN_MAP + dw ItemUseBicycle ; BICYCLE + dw ItemUseSurfboard ; out-of-battle Surf effect + dw ItemUseBall ; SAFARI_BALL + dw ItemUsePokedex ; POKEDEX + dw ItemUseEvoStone ; MOON_STONE + dw ItemUseMedicine ; ANTIDOTE + dw ItemUseMedicine ; BURN_HEAL + dw ItemUseMedicine ; ICE_HEAL + dw ItemUseMedicine ; AWAKENING + dw ItemUseMedicine ; PARLYZ_HEAL + dw ItemUseMedicine ; FULL_RESTORE + dw ItemUseMedicine ; MAX_POTION + dw ItemUseMedicine ; HYPER_POTION + dw ItemUseMedicine ; SUPER_POTION + dw ItemUseMedicine ; POTION + dw ItemUseBait ; BOULDERBADGE + dw ItemUseRock ; CASCADEBADGE + dw UnusableItem ; THUNDERBADGE + dw UnusableItem ; RAINBOWBADGE + dw UnusableItem ; SOULBADGE + dw UnusableItem ; MARSHBADGE + dw UnusableItem ; VOLCANOBADGE + dw UnusableItem ; EARTHBADGE + dw ItemUseEscapeRope ; ESCAPE_ROPE + dw ItemUseRepel ; REPEL + dw UnusableItem ; OLD_AMBER + dw ItemUseEvoStone ; FIRE_STONE + dw ItemUseEvoStone ; THUNDER_STONE + dw ItemUseEvoStone ; WATER_STONE + dw ItemUseVitamin ; HP_UP + dw ItemUseVitamin ; PROTEIN + dw ItemUseVitamin ; IRON + dw ItemUseVitamin ; CARBOS + dw ItemUseVitamin ; CALCIUM + dw ItemUseVitamin ; RARE_CANDY + dw UnusableItem ; DOME_FOSSIL + dw UnusableItem ; HELIX_FOSSIL + dw UnusableItem ; SECRET_KEY + dw UnusableItem + dw UnusableItem ; BIKE_VOUCHER + dw ItemUseXAccuracy ; X_ACCURACY + dw ItemUseEvoStone ; LEAF_STONE + dw ItemUseCardKey ; CARD_KEY + dw UnusableItem ; NUGGET + dw UnusableItem ; ??? PP_UP + dw ItemUsePokedoll ; POKE_DOLL + dw ItemUseMedicine ; FULL_HEAL + dw ItemUseMedicine ; REVIVE + dw ItemUseMedicine ; MAX_REVIVE + dw ItemUseGuardSpec ; GUARD_SPEC + dw ItemUseSuperRepel ; SUPER_REPL + dw ItemUseMaxRepel ; MAX_REPEL + dw ItemUseDireHit ; DIRE_HIT + dw UnusableItem ; COIN + dw ItemUseMedicine ; FRESH_WATER + dw ItemUseMedicine ; SODA_POP + dw ItemUseMedicine ; LEMONADE + dw UnusableItem ; S_S_TICKET + dw UnusableItem ; GOLD_TEETH + dw ItemUseXStat ; X_ATTACK + dw ItemUseXStat ; X_DEFEND + dw ItemUseXStat ; X_SPEED + dw ItemUseXStat ; X_SPECIAL + dw ItemUseCoinCase ; COIN_CASE + dw ItemUseOaksParcel ; OAKS_PARCEL + dw ItemUseItemfinder ; ITEMFINDER + dw UnusableItem ; SILPH_SCOPE + dw ItemUsePokeflute ; POKE_FLUTE + dw UnusableItem ; LIFT_KEY + dw UnusableItem ; EXP_ALL + dw ItemUseOldRod ; OLD_ROD + dw ItemUseGoodRod ; GOOD_ROD + dw ItemUseSuperRod ; SUPER_ROD + dw ItemUsePPUp ; PP_UP (real one) + dw ItemUsePPRestore ; ETHER + dw ItemUsePPRestore ; MAX_ETHER + dw ItemUsePPRestore ; ELIXER + dw ItemUsePPRestore ; MAX_ELIXER + +ItemUseBall: + +; Balls can't be used out of battle. + ld a,[wIsInBattle] + and a + jp z,ItemUseNotTime + +; Balls can't catch trainers' Pokémon. + dec a + jp nz,ThrowBallAtTrainerMon + +; If this is for the old man battle, skip checking if the party & box are full. + ld a,[wBattleType] + dec a + jr z,.canUseBall + + ld a,[wPartyCount] ; is party full? + cp a,PARTY_LENGTH + jr nz,.canUseBall + ld a,[wNumInBox] ; is box full? + cp a,MONS_PER_BOX + jp z,BoxFullCannotThrowBall + +.canUseBall + xor a + ld [wCapturedMonSpecies],a + + ld a,[wBattleType] + cp a,BATTLE_TYPE_SAFARI + jr nz,.skipSafariZoneCode + +.safariZone + ld hl,wNumSafariBalls + dec [hl] ; remove a Safari Ball + +.skipSafariZoneCode + call RunDefaultPaletteCommand + + ld a,$43 ; successful capture value + ld [wPokeBallAnimData],a + + call LoadScreenTilesFromBuffer1 + ld hl,ItemUseText00 + call PrintText + +; If the player is fighting an unidentified ghost, set the value that indicates +; the Pokémon can't be caught and skip the capture calculations. + callab IsGhostBattle + ld b,$10 ; can't be caught value + jp z,.setAnimData + + ld a,[wBattleType] + dec a + jr nz,.notOldManBattle + +.oldManBattle + ld hl,wGrassRate + ld de,wPlayerName + ld bc,NAME_LENGTH + call CopyData ; save the player's name in the Wild Monster data (part of the Cinnabar Island Missingno. glitch) + jp .captured + +.notOldManBattle +; If the player is fighting the ghost Marowak, set the value that indicates the +; Pokémon can't be caught and skip the capture calculations. + ld a,[wCurMap] + cp a,POKEMONTOWER_6 + jr nz,.loop + ld a,[wEnemyMonSpecies2] + cp a,MAROWAK + ld b,$10 ; can't be caught value + jp z,.setAnimData + +; Get the first random number. Let it be called Rand1. +; Rand1 must be within a certain range according the kind of ball being thrown. +; The ranges are as follows. +; Poké Ball: [0, 255] +; Great Ball: [0, 200] +; Ultra/Safari Ball: [0, 150] +; Loop until an acceptable number is found. + +.loop + call Random + ld b,a + +; Get the item ID. + ld hl,wcf91 + ld a,[hl] + +; The Master Ball always succeeds. + cp a,MASTER_BALL + jp z,.captured + +; Anything will do for the basic Poké Ball. + cp a,POKE_BALL + jr z,.checkForAilments + +; If it's a Great/Ultra/Safari Ball and Rand1 is greater than 200, try again. + ld a,200 + cp b + jr c,.loop + +; Less than or equal to 200 is good enough for a Great Ball. + ld a,[hl] + cp a,GREAT_BALL + jr z,.checkForAilments + +; If it's an Ultra/Safari Ball and Rand1 is greater than 150, try again. + ld a,150 + cp b + jr c,.loop + +.checkForAilments +; Pokémon can be caught more easily with a status ailment. +; Depending on the status ailment, a certain value will be subtracted from +; Rand1. Let this value be called Status. +; The larger Status is, the more easily the Pokémon can be caught. +; no status ailment: Status = 0 +; Burn/Paralysis/Poison: Status = 12 +; Freeze/Sleep: Status = 25 +; If Status is greater than Rand1, the Pokémon will be caught for sure. + ld a,[wEnemyMonStatus] + and a + jr z,.skipAilmentValueSubtraction ; no ailments + and a, 1 << FRZ | SLP + ld c,12 + jr z,.notFrozenOrAsleep + ld c,25 +.notFrozenOrAsleep + ld a,b + sub c + jp c,.captured + ld b,a + +.skipAilmentValueSubtraction + push bc ; save (Rand1 - Status) + +; Calculate MaxHP * 255. + xor a + ld [H_MULTIPLICAND],a + ld hl,wEnemyMonMaxHP + ld a,[hli] + ld [H_MULTIPLICAND + 1],a + ld a,[hl] + ld [H_MULTIPLICAND + 2],a + ld a,255 + ld [H_MULTIPLIER],a + call Multiply + +; Determine BallFactor. It's 8 for Great Balls and 12 for the others. + ld a,[wcf91] + cp a,GREAT_BALL + ld a,12 + jr nz,.skip1 + ld a,8 + +.skip1 +; Note that the results of all division operations are floored. + +; Calculate (MaxHP * 255) / BallFactor. + ld [H_DIVISOR],a + ld b,4 ; number of bytes in dividend + call Divide + +; Divide the enemy's current HP by 4. HP is not supposed to exceed 999 so +; the result should fit in a. If the division results in a quotient of 0, +; change it to 1. + ld hl,wEnemyMonHP + ld a,[hli] + ld b,a + ld a,[hl] + srl b + rr a + srl b + rr a + and a + jr nz,.skip2 + inc a + +.skip2 +; Let W = ((MaxHP * 255) / BallFactor) / max(HP / 4, 1). Calculate W. + ld [H_DIVISOR],a + ld b,4 + call Divide + +; If W > 255, store 255 in [H_QUOTIENT + 3]. +; Let X = min(W, 255) = [H_QUOTIENT + 3]. + ld a,[H_QUOTIENT + 2] + and a + jr z,.skip3 + ld a,255 + ld [H_QUOTIENT + 3],a + +.skip3 + pop bc ; b = Rand1 - Status + +; If Rand1 - Status > CatchRate, the ball fails to capture the Pokémon. + ld a,[wEnemyMonCatchRate] + cp b + jr c,.failedToCapture + +; If W > 255, the ball captures the Pokémon. + ld a,[H_QUOTIENT + 2] + and a + jr nz,.captured + + call Random ; Let this random number be called Rand2. + +; If Rand2 > X, the ball fails to capture the Pokémon. + ld b,a + ld a,[H_QUOTIENT + 3] + cp b + jr c,.failedToCapture + +.captured + jr .skipShakeCalculations + +.failedToCapture + ld a,[H_QUOTIENT + 3] + ld [wPokeBallCaptureCalcTemp],a ; Save X. + +; Calculate CatchRate * 100. + xor a + ld [H_MULTIPLICAND],a + ld [H_MULTIPLICAND + 1],a + ld a,[wEnemyMonCatchRate] + ld [H_MULTIPLICAND + 2],a + ld a,100 + ld [H_MULTIPLIER],a + call Multiply + +; Determine BallFactor2. +; Poké Ball: BallFactor2 = 255 +; Great Ball: BallFactor2 = 200 +; Ultra/Safari Ball: BallFactor2 = 150 + ld a,[wcf91] + ld b,255 + cp a,POKE_BALL + jr z,.skip4 + ld b,200 + cp a,GREAT_BALL + jr z,.skip4 + ld b,150 + cp a,ULTRA_BALL + jr z,.skip4 + +.skip4 +; Let Y = (CatchRate * 100) / BallFactor2. Calculate Y. + ld a,b + ld [H_DIVISOR],a + ld b,4 + call Divide + +; If Y > 255, there are 3 shakes. +; Note that this shouldn't be possible. +; The maximum value of Y is (255 * 100) / 150 = 170. + ld a,[H_QUOTIENT + 2] + and a + ld b,$63 ; 3 shakes + jr nz,.setAnimData + +; Calculate X * Y. + ld a,[wPokeBallCaptureCalcTemp] + ld [H_MULTIPLIER],a + call Multiply + +; Calculate (X * Y) / 255. + ld a,255 + ld [H_DIVISOR],a + ld b,4 + call Divide + +; Determine Status2. +; no status ailment: Status2 = 0 +; Burn/Paralysis/Poison: Status2 = 5 +; Freeze/Sleep: Status2 = 10 + ld a,[wEnemyMonStatus] + and a + jr z,.skip5 + and a, 1 << FRZ | SLP + ld b,5 + jr z,.addAilmentValue + ld b,10 + +.addAilmentValue +; If the Pokémon has a status ailment, add Status2. + ld a,[H_QUOTIENT + 3] + add b + ld [H_QUOTIENT + 3],a + +.skip5 +; Finally determine the number of shakes. +; Let Z = ((X * Y) / 255) + Status2 = [H_QUOTIENT + 3]. +; The number of shakes depend on the range Z is in. +; 0 ≤ Z < 10: 0 shakes (the ball misses) +; 10 ≤ Z < 30: 1 shake +; 30 ≤ Z < 70: 2 shakes +; 70 ≤ Z: 3 shakes + ld a,[H_QUOTIENT + 3] + cp a,10 + ld b,$20 + jr c,.setAnimData + cp a,30 + ld b,$61 + jr c,.setAnimData + cp a,70 + ld b,$62 + jr c,.setAnimData + ld b,$63 + +.setAnimData + ld a,b + ld [wPokeBallAnimData],a + +.skipShakeCalculations + ld c,20 + call DelayFrames + +; Do the animation. + ld a,TOSS_ANIM + ld [wAnimationID],a + xor a + ld [H_WHOSETURN],a + ld [wAnimationType],a + ld [wDamageMultipliers],a + ld a,[wWhichPokemon] + push af + ld a,[wcf91] + push af + predef MoveAnimation + pop af + ld [wcf91],a + pop af + ld [wWhichPokemon],a + +; Determine the message to display from the animation. + ld a,[wPokeBallAnimData] + cp a,$10 + ld hl,ItemUseBallText00 + jp z,.printMessage + cp a,$20 + ld hl,ItemUseBallText01 + jp z,.printMessage + cp a,$61 + ld hl,ItemUseBallText02 + jp z,.printMessage + cp a,$62 + ld hl,ItemUseBallText03 + jp z,.printMessage + cp a,$63 + ld hl,ItemUseBallText04 + jp z,.printMessage + +; Save current HP. + ld hl,wEnemyMonHP + ld a,[hli] + push af + ld a,[hli] + push af + +; Save status ailment. + inc hl + ld a,[hl] + push af + + push hl + +; If the Pokémon is transformed, the Pokémon is assumed to be a Ditto. +; This is a bug because a wild Pokémon could have used Transform via +; Mirror Move even though the only wild Pokémon that knows Transform is Ditto. + ld hl,wEnemyBattleStatus3 + bit TRANSFORMED,[hl] + jr z,.notTransformed + ld a,DITTO + ld [wEnemyMonSpecies2],a + jr .skip6 + +.notTransformed +; If the Pokémon is not transformed, set the transformed bit and copy the +; DVs to wTransformedEnemyMonOriginalDVs so that LoadEnemyMonData won't generate +; new DVs. + set TRANSFORMED,[hl] + ld hl,wTransformedEnemyMonOriginalDVs + ld a,[wEnemyMonDVs] + ld [hli],a + ld a,[wEnemyMonDVs + 1] + ld [hl],a + +.skip6 + ld a,[wcf91] + push af + ld a,[wEnemyMonSpecies2] + ld [wcf91],a + ld a,[wEnemyMonLevel] + ld [wCurEnemyLVL],a + callab LoadEnemyMonData + pop af + ld [wcf91],a + pop hl + pop af + ld [hld],a + dec hl + pop af + ld [hld],a + pop af + ld [hl],a + ld a,[wEnemyMonSpecies] + ld [wCapturedMonSpecies],a + ld [wcf91],a + ld [wd11e],a + ld a,[wBattleType] + dec a ; is this the old man battle? + jr z,.oldManCaughtMon ; if so, don't give the player the caught Pokémon + + ld hl,ItemUseBallText05 + call PrintText + +; Add the caught Pokémon to the Pokédex. + predef IndexToPokedex + ld a,[wd11e] + dec a + ld c,a + ld b,FLAG_TEST + ld hl,wPokedexOwned + predef FlagActionPredef + ld a,c + push af + ld a,[wd11e] + dec a + ld c,a + ld b,FLAG_SET + predef FlagActionPredef + pop af + + and a ; was the Pokémon already in the Pokédex? + jr nz,.skipShowingPokedexData ; if so, don't show the Pokédex data + + ld hl,ItemUseBallText06 + call PrintText + call ClearSprites + ld a,[wEnemyMonSpecies] + ld [wd11e],a + predef ShowPokedexData + +.skipShowingPokedexData + ld a,[wPartyCount] + cp a,PARTY_LENGTH ; is party full? + jr z,.sendToBox + xor a ; PLAYER_PARTY_DATA + ld [wMonDataLocation],a + call ClearSprites + call AddPartyMon + jr .done + +.sendToBox + call ClearSprites + call SendNewMonToBox + ld hl,ItemUseBallText07 + CheckEvent EVENT_MET_BILL + jr nz,.printTransferredToPCText + ld hl,ItemUseBallText08 +.printTransferredToPCText + call PrintText + jr .done + +.oldManCaughtMon + ld hl,ItemUseBallText05 + +.printMessage + call PrintText + call ClearSprites + +.done + ld a,[wBattleType] + and a ; is this the old man battle? + ret nz ; if so, don't remove a ball from the bag + +; Remove a ball from the bag. + ld hl,wNumBagItems + inc a + ld [wItemQuantity],a + jp RemoveItemFromInventory + +ItemUseBallText00: +;"It dodged the thrown ball!" +;"This pokemon can't be caught" + TX_FAR _ItemUseBallText00 + db "@" +ItemUseBallText01: +;"You missed the pokemon!" + TX_FAR _ItemUseBallText01 + db "@" +ItemUseBallText02: +;"Darn! The pokemon broke free!" + TX_FAR _ItemUseBallText02 + db "@" +ItemUseBallText03: +;"Aww! It appeared to be caught!" + TX_FAR _ItemUseBallText03 + db "@" +ItemUseBallText04: +;"Shoot! It was so close too!" + TX_FAR _ItemUseBallText04 + db "@" +ItemUseBallText05: +;"All right! {MonName} was caught!" +;play sound + TX_FAR _ItemUseBallText05 + TX_SFX_CAUGHT_MON + TX_BLINK + db "@" +ItemUseBallText07: +;"X was transferred to Bill's PC" + TX_FAR _ItemUseBallText07 + db "@" +ItemUseBallText08: +;"X was transferred to someone's PC" + TX_FAR _ItemUseBallText08 + db "@" + +ItemUseBallText06: +;"New DEX data will be added..." +;play sound + TX_FAR _ItemUseBallText06 + TX_SFX_DEX_PAGE_ADDED + TX_BLINK + db "@" + +ItemUseTownMap: + ld a,[wIsInBattle] + and a + jp nz,ItemUseNotTime + jpba DisplayTownMap + +ItemUseBicycle: + ld a,[wIsInBattle] + and a + jp nz,ItemUseNotTime + ld a,[wWalkBikeSurfState] + ld [wWalkBikeSurfStateCopy],a + cp a,2 ; is the player surfing? + jp z,ItemUseNotTime + dec a ; is player already bicycling? + jr nz,.tryToGetOnBike +.getOffBike + call ItemUseReloadOverworldData + xor a + ld [wWalkBikeSurfState],a ; change player state to walking + call PlayDefaultMusic ; play walking music + ld hl,GotOffBicycleText + jr .printText +.tryToGetOnBike + call IsBikeRidingAllowed + jp nc,NoCyclingAllowedHere + call ItemUseReloadOverworldData + xor a ; no keys pressed + ld [hJoyHeld],a ; current joypad state + inc a + ld [wWalkBikeSurfState],a ; change player state to bicycling + ld hl,GotOnBicycleText + call PlayDefaultMusic ; play bike riding music +.printText + jp PrintText + +; used for Surf out-of-battle effect +ItemUseSurfboard: + ld a,[wWalkBikeSurfState] + ld [wWalkBikeSurfStateCopy],a + cp a,2 ; is the player already surfing? + jr z,.tryToStopSurfing +.tryToSurf + call IsNextTileShoreOrWater + jp c,SurfingAttemptFailed + ld hl,TilePairCollisionsWater + call CheckForTilePairCollisions + jp c,SurfingAttemptFailed +.surf + call .makePlayerMoveForward + ld hl,wd730 + set 7,[hl] + ld a,2 + ld [wWalkBikeSurfState],a ; change player state to surfing + call PlayDefaultMusic ; play surfing music + ld hl,SurfingGotOnText + jp PrintText +.tryToStopSurfing + xor a + ld [hSpriteIndexOrTextID],a + ld d,16 ; talking range in pixels (normal range) + call IsSpriteInFrontOfPlayer2 + res 7,[hl] + ld a,[hSpriteIndexOrTextID] + and a ; is there a sprite in the way? + jr nz,.cannotStopSurfing + ld hl,TilePairCollisionsWater + call CheckForTilePairCollisions + jr c,.cannotStopSurfing + ld hl,wTilesetCollisionPtr ; pointer to list of passable tiles + ld a,[hli] + ld h,[hl] + ld l,a ; hl now points to passable tiles + ld a,[wTileInFrontOfPlayer] ; tile in front of the player + ld b,a +.passableTileLoop + ld a,[hli] + cp b + jr z,.stopSurfing + cp a,$ff + jr nz,.passableTileLoop +.cannotStopSurfing + ld hl,SurfingNoPlaceToGetOffText + jp PrintText +.stopSurfing + call .makePlayerMoveForward + ld hl,wd730 + set 7,[hl] + xor a + ld [wWalkBikeSurfState],a ; change player state to walking + dec a + ld [wJoyIgnore],a + call PlayDefaultMusic ; play walking music + jp LoadWalkingPlayerSpriteGraphics +; uses a simulated button press to make the player move forward +.makePlayerMoveForward + ld a,[wPlayerDirection] ; direction the player is going + bit PLAYER_DIR_BIT_UP,a + ld b,D_UP + jr nz,.storeSimulatedButtonPress + bit PLAYER_DIR_BIT_DOWN,a + ld b,D_DOWN + jr nz,.storeSimulatedButtonPress + bit PLAYER_DIR_BIT_LEFT,a + ld b,D_LEFT + jr nz,.storeSimulatedButtonPress + ld b,D_RIGHT +.storeSimulatedButtonPress + ld a,b + ld [wSimulatedJoypadStatesEnd],a + xor a + ld [wWastedByteCD39],a + inc a + ld [wSimulatedJoypadStatesIndex],a + ret + +SurfingGotOnText: + TX_FAR _SurfingGotOnText + db "@" + +SurfingNoPlaceToGetOffText: + TX_FAR _SurfingNoPlaceToGetOffText + db "@" + +ItemUsePokedex: + predef_jump ShowPokedexMenu + +ItemUseEvoStone: + ld a,[wIsInBattle] + and a + jp nz,ItemUseNotTime + ld a,[wWhichPokemon] + push af + ld a,[wcf91] + ld [wEvoStoneItemID],a + push af + ld a,EVO_STONE_PARTY_MENU + ld [wPartyMenuTypeOrMessageID],a + ld a,$ff + ld [wUpdateSpritesEnabled],a + call DisplayPartyMenu + pop bc + jr c,.canceledItemUse + ld a,b + ld [wcf91],a + ld a,$01 + ld [wForceEvolution],a + ld a,SFX_HEAL_AILMENT + call PlaySoundWaitForCurrent + call WaitForSoundToFinish + callab TryEvolvingMon ; try to evolve pokemon + ld a,[wEvolutionOccurred] + and a + jr z,.noEffect + pop af + ld [wWhichPokemon],a + ld hl,wNumBagItems + ld a,1 ; remove 1 stone + ld [wItemQuantity],a + jp RemoveItemFromInventory +.noEffect + call ItemUseNoEffect +.canceledItemUse + xor a + ld [wActionResultOrTookBattleTurn],a ; item not used + pop af + ret + +ItemUseVitamin: + ld a,[wIsInBattle] + and a + jp nz,ItemUseNotTime + +ItemUseMedicine: + ld a,[wPartyCount] + and a + jp z,.emptyParty + ld a,[wWhichPokemon] + push af + ld a,[wcf91] + push af + ld a,USE_ITEM_PARTY_MENU + ld [wPartyMenuTypeOrMessageID],a + ld a,$ff + ld [wUpdateSpritesEnabled],a + ld a,[wPseudoItemID] + and a ; using Softboiled? + jr z,.notUsingSoftboiled +; if using softboiled + call GoBackToPartyMenu + jr .getPartyMonDataAddress +.emptyParty + ld hl,.emptyPartyText + xor a + ld [wActionResultOrTookBattleTurn],a ; item use failed + jp PrintText +.emptyPartyText + text "Du besitzt noch" + line "keine #MON!" + prompt +.notUsingSoftboiled + call DisplayPartyMenu +.getPartyMonDataAddress + jp c,.canceledItemUse + ld hl,wPartyMons + ld bc,wPartyMon2 - wPartyMon1 + ld a,[wWhichPokemon] + call AddNTimes + ld a,[wWhichPokemon] + ld [wUsedItemOnWhichPokemon],a + ld d,a + ld a,[wcf91] + ld e,a + ld [wd0b5],a + pop af + ld [wcf91],a + pop af + ld [wWhichPokemon],a + ld a,[wPseudoItemID] + and a ; using Softboiled? + jr z,.checkItemType +; if using softboiled + ld a,[wWhichPokemon] + cp d ; is the pokemon trying to use softboiled on itself? + jr z,ItemUseMedicine ; if so, force another choice +.checkItemType + ld a,[wcf91] + cp a,REVIVE + jr nc,.healHP ; if it's a Revive or Max Revive + cp a,FULL_HEAL + jr z,.cureStatusAilment ; if it's a Full Heal + cp a,HP_UP + jp nc,.useVitamin ; if it's a vitamin or Rare Candy + cp a,FULL_RESTORE + jr nc,.healHP ; if it's a Full Restore or one of the potions +; fall through if it's one of the status-specific healing items +.cureStatusAilment + ld bc,wPartyMon1Status - wPartyMon1 + add hl,bc ; hl now points to status + ld a,[wcf91] + lb bc, ANTIDOTE_MSG, 1 << PSN + cp a,ANTIDOTE + jr z,.checkMonStatus + lb bc, BURN_HEAL_MSG, 1 << BRN + cp a,BURN_HEAL + jr z,.checkMonStatus + lb bc, ICE_HEAL_MSG, 1 << FRZ + cp a,ICE_HEAL + jr z,.checkMonStatus + lb bc, AWAKENING_MSG, SLP + cp a,AWAKENING + jr z,.checkMonStatus + lb bc, PARALYZ_HEAL_MSG, 1 << PAR + cp a,PARLYZ_HEAL + jr z,.checkMonStatus + lb bc, FULL_HEAL_MSG, $ff ; Full Heal +.checkMonStatus + ld a,[hl] ; pokemon's status + and c ; does the pokemon have a status ailment the item can cure? + jp z,.healingItemNoEffect +; if the pokemon has a status the item can heal + xor a + ld [hl],a ; remove the status ailment in the party data + ld a,b + ld [wPartyMenuTypeOrMessageID],a ; the message to display for the item used + ld a,[wPlayerMonNumber] + cp d ; is pokemon the item was used on active in battle? + jp nz,.doneHealing +; if it is active in battle + xor a + ld [wBattleMonStatus],a ; remove the status ailment in the in-battle pokemon data + push hl + ld hl,wPlayerBattleStatus3 + res BADLY_POISONED,[hl] ; heal Toxic status + pop hl + ld bc,wPartyMon1Stats - wPartyMon1Status + add hl,bc ; hl now points to party stats + ld de,wBattleMonStats + ld bc,NUM_STATS * 2 + call CopyData ; copy party stats to in-battle stat data + predef DoubleOrHalveSelectedStats + jp .doneHealing +.healHP + inc hl ; hl = address of current HP + ld a,[hli] + ld b,a + ld [wHPBarOldHP+1],a + ld a,[hl] + ld c,a + ld [wHPBarOldHP],a ; current HP stored at wHPBarOldHP (2 bytes, big-endian) + or b + jr nz,.notFainted +.fainted + ld a,[wcf91] + cp a,REVIVE + jr z,.updateInBattleFaintedData + cp a,MAX_REVIVE + jr z,.updateInBattleFaintedData + jp .healingItemNoEffect +.updateInBattleFaintedData + ld a,[wIsInBattle] + and a + jr z,.compareCurrentHPToMaxHP + push hl + push de + push bc + ld a,[wUsedItemOnWhichPokemon] + ld c,a + ld hl,wPartyFoughtCurrentEnemyFlags + ld b,FLAG_TEST + predef FlagActionPredef + ld a,c + and a + jr z,.next + ld a,[wUsedItemOnWhichPokemon] + ld c,a + ld hl,wPartyGainExpFlags + ld b,FLAG_SET + predef FlagActionPredef +.next + pop bc + pop de + pop hl + jr .compareCurrentHPToMaxHP +.notFainted + ld a,[wcf91] + cp a,REVIVE + jp z,.healingItemNoEffect + cp a,MAX_REVIVE + jp z,.healingItemNoEffect +.compareCurrentHPToMaxHP + push hl + push bc + ld bc,wPartyMon1MaxHP - (wPartyMon1HP + 1) + add hl,bc ; hl now points to max HP + pop bc + ld a,[hli] + cp b + jr nz,.skipComparingLSB ; no need to compare the LSB's if the MSB's don't match + ld a,[hl] + cp c +.skipComparingLSB + pop hl + jr nz,.notFullHP +.fullHP ; if the pokemon's current HP equals its max HP + ld a,[wcf91] + cp a,FULL_RESTORE + jp nz,.healingItemNoEffect + inc hl + inc hl + ld a,[hld] ; status ailment + and a ; does the pokemon have a status ailment? + jp z,.healingItemNoEffect + ld a,FULL_HEAL + ld [wcf91],a + dec hl + dec hl + dec hl + jp .cureStatusAilment +.notFullHP ; if the pokemon's current HP doesn't equal its max HP + xor a + ld [wLowHealthAlarm],a ;disable low health alarm + ld [wChannelSoundIDs + Ch4],a + push hl + push de + ld bc,wPartyMon1MaxHP - (wPartyMon1HP + 1) + add hl,bc ; hl now points to max HP + ld a,[hli] + ld [wHPBarMaxHP+1],a + ld a,[hl] + ld [wHPBarMaxHP],a ; max HP stored at wHPBarMaxHP (2 bytes, big-endian) + ld a,[wPseudoItemID] + and a ; using Softboiled? + jp z,.notUsingSoftboiled2 +; if using softboiled + ld hl,wHPBarMaxHP + ld a,[hli] + push af + ld a,[hli] + push af + ld a,[hli] + push af + ld a,[hl] + push af + ld hl,wPartyMon1MaxHP + ld a,[wWhichPokemon] + ld bc,wPartyMon2 - wPartyMon1 + call AddNTimes + ld a,[hli] + ld [wHPBarMaxHP + 1],a + ld [H_DIVIDEND],a + ld a,[hl] + ld [wHPBarMaxHP],a + ld [H_DIVIDEND + 1],a + ld a,5 + ld [H_DIVISOR],a + ld b,2 ; number of bytes + call Divide ; get 1/5 of max HP of pokemon that used Softboiled + ld bc,(wPartyMon1HP + 1) - (wPartyMon1MaxHP + 1) + add hl,bc ; hl now points to LSB of current HP of pokemon that used Softboiled +; subtract 1/5 of max HP from current HP of pokemon that used Softboiled + ld a,[H_QUOTIENT + 3] + push af + ld b,a + ld a,[hl] + ld [wHPBarOldHP],a + sub b + ld [hld],a + ld [wHPBarNewHP],a + ld a,[H_QUOTIENT + 2] + ld b,a + ld a,[hl] + ld [wHPBarOldHP+1],a + sbc b + ld [hl],a + ld [wHPBarNewHP+1],a + coord hl, 4, 1 + ld a,[wWhichPokemon] + ld bc,2 * SCREEN_WIDTH + call AddNTimes ; calculate coordinates of HP bar of pokemon that used Softboiled + ld a,SFX_HEAL_HP + call PlaySoundWaitForCurrent + ld a,[hFlags_0xFFF6] + set 0,a + ld [hFlags_0xFFF6],a + ld a,$02 + ld [wHPBarType],a + predef UpdateHPBar2 ; animate HP bar decrease of pokemon that used Softboiled + ld a,[hFlags_0xFFF6] + res 0,a + ld [hFlags_0xFFF6],a + pop af + ld b,a ; store heal amount (1/5 of max HP) + ld hl,wHPBarOldHP + 1 + pop af + ld [hld],a + pop af + ld [hld],a + pop af + ld [hld],a + pop af + ld [hl],a + jr .addHealAmount +.notUsingSoftboiled2 + ld a,[wcf91] + cp a,SODA_POP + ld b,60 ; Soda Pop heal amount + jr z,.addHealAmount + ld b,80 ; Lemonade heal amount + jr nc,.addHealAmount + cp a,FRESH_WATER + ld b,50 ; Fresh Water heal amount + jr z,.addHealAmount + cp a,SUPER_POTION + ld b,200 ; Hyper Potion heal amount + jr c,.addHealAmount + ld b,50 ; Super Potion heal amount + jr z,.addHealAmount + ld b,20 ; Potion heal amount +.addHealAmount + pop de + pop hl + ld a,[hl] + add b + ld [hld],a + ld [wHPBarNewHP],a + ld a,[hl] + ld [wHPBarNewHP+1],a + jr nc,.noCarry + inc [hl] + ld a,[hl] + ld [wHPBarNewHP + 1],a +.noCarry + push de + inc hl + ld d,h + ld e,l ; de now points to current HP + ld hl,(wPartyMon1MaxHP + 1) - (wPartyMon1HP + 1) + add hl,de ; hl now points to max HP + ld a,[wcf91] + cp a,REVIVE + jr z,.setCurrentHPToHalfMaxHP + ld a,[hld] + ld b,a + ld a,[de] + sub b + dec de + ld b,[hl] + ld a,[de] + sbc b + jr nc,.setCurrentHPToMaxHp ; if current HP exceeds max HP after healing + ld a,[wcf91] + cp a,HYPER_POTION + jr c,.setCurrentHPToMaxHp ; if using a Full Restore or Max Potion + cp a,MAX_REVIVE + jr z,.setCurrentHPToMaxHp ; if using a Max Revive + jr .updateInBattleData +.setCurrentHPToHalfMaxHP + dec hl + dec de + ld a,[hli] + srl a + ld [de],a + ld [wHPBarNewHP+1],a + ld a,[hl] + rr a + inc de + ld [de],a + ld [wHPBarNewHP],a + dec de + jr .doneHealingPartyHP +.setCurrentHPToMaxHp + ld a,[hli] + ld [de],a + ld [wHPBarNewHP+1],a + inc de + ld a,[hl] + ld [de],a + ld [wHPBarNewHP],a + dec de +.doneHealingPartyHP ; done updating the pokemon's current HP in the party data structure + ld a,[wcf91] + cp a,FULL_RESTORE + jr nz,.updateInBattleData + ld bc,wPartyMon1Status - (wPartyMon1MaxHP + 1) + add hl,bc + xor a + ld [hl],a ; remove the status ailment in the party data +.updateInBattleData + ld h,d + ld l,e + pop de + ld a,[wPlayerMonNumber] + cp d ; is pokemon the item was used on active in battle? + jr nz,.calculateHPBarCoords +; copy party HP to in-battle HP + ld a,[hli] + ld [wBattleMonHP],a + ld a,[hld] + ld [wBattleMonHP + 1],a + ld a,[wcf91] + cp a,FULL_RESTORE + jr nz,.calculateHPBarCoords + xor a + ld [wBattleMonStatus],a ; remove the status ailment in the in-battle pokemon data +.calculateHPBarCoords + ld hl,wOAMBuffer + $90 + ld bc,2 * SCREEN_WIDTH + inc d +.calculateHPBarCoordsLoop + add hl,bc + dec d + jr nz,.calculateHPBarCoordsLoop + jr .doneHealing +.healingItemNoEffect + call ItemUseNoEffect + jp .done +.doneHealing + ld a,[wPseudoItemID] + and a ; using Softboiled? + jr nz,.skipRemovingItem ; no item to remove if using Softboiled + push hl + call RemoveUsedItem + pop hl +.skipRemovingItem + ld a,[wcf91] + cp a,FULL_RESTORE + jr c,.playStatusAilmentCuringSound + cp a,FULL_HEAL + jr z,.playStatusAilmentCuringSound + ld a,SFX_HEAL_HP + call PlaySoundWaitForCurrent + ld a,[hFlags_0xFFF6] + set 0,a + ld [hFlags_0xFFF6],a + ld a,$02 + ld [wHPBarType],a + predef UpdateHPBar2 ; animate the HP bar lengthening + ld a,[hFlags_0xFFF6] + res 0,a + ld [hFlags_0xFFF6],a + ld a,REVIVE_MSG + ld [wPartyMenuTypeOrMessageID],a + ld a,[wcf91] + cp a,REVIVE + jr z,.showHealingItemMessage + cp a,MAX_REVIVE + jr z,.showHealingItemMessage + ld a,POTION_MSG + ld [wPartyMenuTypeOrMessageID],a + jr .showHealingItemMessage +.playStatusAilmentCuringSound + ld a,SFX_HEAL_AILMENT + call PlaySoundWaitForCurrent +.showHealingItemMessage + xor a + ld [H_AUTOBGTRANSFERENABLED],a + call ClearScreen + dec a + ld [wUpdateSpritesEnabled],a + call RedrawPartyMenu ; redraws the party menu and displays the message + ld a,1 + ld [H_AUTOBGTRANSFERENABLED],a + ld c,50 + call DelayFrames + call WaitForTextScrollButtonPress + jr .done +.canceledItemUse + xor a + ld [wActionResultOrTookBattleTurn],a ; item use failed + pop af + pop af +.done + ld a,[wPseudoItemID] + and a ; using Softboiled? + ret nz ; if so, return + call GBPalWhiteOut + call z,RunDefaultPaletteCommand + ld a,[wIsInBattle] + and a + ret nz + jp ReloadMapData +.useVitamin + push hl + ld a,[hl] + ld [wd0b5],a + ld [wd11e],a + ld bc,wPartyMon1Level - wPartyMon1 + add hl,bc ; hl now points to level + ld a,[hl] ; a = level + ld [wCurEnemyLVL],a ; store level + call GetMonHeader + push de + ld a,d + ld hl,wPartyMonNicks + call GetPartyMonName + pop de + pop hl + ld a,[wcf91] + cp a,RARE_CANDY + jp z,.useRareCandy + push hl + sub a,HP_UP + add a + ld bc,wPartyMon1HPExp - wPartyMon1 + add hl,bc + add l + ld l,a + jr nc,.noCarry2 + inc h +.noCarry2 + ld a,10 + ld b,a + ld a,[hl] ; a = MSB of stat experience of the appropriate stat + cp a,100 ; is there already at least 25600 (256 * 100) stat experience? + jr nc,.vitaminNoEffect ; if so, vitamins can't add any more + add b ; add 2560 (256 * 10) stat experience + jr nc,.noCarry3 ; a carry should be impossible here, so this will always jump + ld a,255 +.noCarry3 + ld [hl],a + pop hl + call .recalculateStats + ld hl,VitaminText + ld a,[wcf91] + sub a,HP_UP - 1 + ld c,a +.statNameLoop ; loop to get the address of the name of the stat the vitamin increases + dec c + jr z,.gotStatName +.statNameInnerLoop + ld a,[hli] + ld b,a + ld a,$50 + cp b + jr nz,.statNameInnerLoop + jr .statNameLoop +.gotStatName + ld de,wcf50 + ld bc,10 + call CopyData ; copy the stat's name to wcf50 + ld a,SFX_HEAL_AILMENT + call PlaySound + ld hl,VitaminStatRoseText + call PrintText + jp RemoveUsedItem +.vitaminNoEffect + pop hl + ld hl,VitaminNoEffectText + call PrintText + jp GBPalWhiteOut +.recalculateStats + ld bc,wPartyMon1Stats - wPartyMon1 + add hl,bc + ld d,h + ld e,l ; de now points to stats + ld bc,(wPartyMon1Exp + 2) - wPartyMon1Stats + add hl,bc ; hl now points to LSB of experience + ld b,1 + jp CalcStats ; recalculate stats +.useRareCandy + push hl + ld bc,wPartyMon1Level - wPartyMon1 + add hl,bc ; hl now points to level + ld a,[hl] ; a = level + cp a, MAX_LEVEL + jr z,.vitaminNoEffect ; can't raise level above 100 + inc a + ld [hl],a ; store incremented level + ld [wCurEnemyLVL],a + push hl + push de + ld d,a + callab CalcExperience ; calculate experience for next level and store it at $ff96 + pop de + pop hl + ld bc,wPartyMon1Exp - wPartyMon1Level + add hl,bc ; hl now points to MSB of experience +; update experience to minimum for new level + ld a,[hExperience] + ld [hli],a + ld a,[hExperience + 1] + ld [hli],a + ld a,[hExperience + 2] + ld [hl],a + pop hl + ld a,[wWhichPokemon] + push af + ld a,[wcf91] + push af + push de + push hl + ld bc,wPartyMon1MaxHP - wPartyMon1 + add hl,bc ; hl now points to MSB of max HP + ld a,[hli] + ld b,a + ld c,[hl] + pop hl + push bc + push hl + call .recalculateStats + pop hl + ld bc,(wPartyMon1MaxHP + 1) - wPartyMon1 + add hl,bc ; hl now points to LSB of max HP + pop bc + ld a,[hld] + sub c + ld c,a + ld a,[hl] + sbc b + ld b,a ; bc = the amount of max HP gained from leveling up +; add the amount gained to the current HP + ld de,(wPartyMon1HP + 1) - wPartyMon1MaxHP + add hl,de ; hl now points to LSB of current HP + ld a,[hl] + add c + ld [hld],a + ld a,[hl] + adc b + ld [hl],a + ld a,RARE_CANDY_MSG + ld [wPartyMenuTypeOrMessageID],a + call RedrawPartyMenu + pop de + ld a,d + ld [wWhichPokemon],a + ld a,e + ld [wd11e],a + xor a ; PLAYER_PARTY_DATA + ld [wMonDataLocation],a + call LoadMonData + ld d,$01 + callab PrintStatsBox ; display new stats text box + call WaitForTextScrollButtonPress ; wait for button press + xor a ; PLAYER_PARTY_DATA + ld [wMonDataLocation],a + predef LearnMoveFromLevelUp ; learn level up move, if any + xor a + ld [wForceEvolution],a + callab TryEvolvingMon ; evolve pokemon, if appropriate + ld a,$01 + ld [wUpdateSpritesEnabled],a + pop af + ld [wcf91],a + pop af + ld [wWhichPokemon],a + jp RemoveUsedItem + +VitaminStatRoseText: + TX_FAR _VitaminStatRoseText + db "@" + +VitaminNoEffectText: + TX_FAR _VitaminNoEffectText + db "@" + +VitaminText: + db "GESU@" + db "ANGR@" + db "VERT@" + db "INIT@" + db "SPEZ@" + +ItemUseBait: + ld hl,ThrewBaitText + call PrintText + ld hl,wEnemyMonCatchRate ; catch rate + srl [hl] ; halve catch rate + ld a,BAIT_ANIM + ld hl,wSafariBaitFactor ; bait factor + ld de,wSafariEscapeFactor ; escape factor + jr BaitRockCommon + +ItemUseRock: + ld hl,ThrewRockText + call PrintText + ld hl,wEnemyMonCatchRate ; catch rate + ld a,[hl] + add a ; double catch rate + jr nc,.noCarry + ld a,$ff +.noCarry + ld [hl],a + ld a,ROCK_ANIM + ld hl,wSafariEscapeFactor ; escape factor + ld de,wSafariBaitFactor ; bait factor + +BaitRockCommon: + ld [wAnimationID],a + xor a + ld [wAnimationType],a + ld [H_WHOSETURN],a + ld [de],a ; zero escape factor (for bait), zero bait factor (for rock) +.randomLoop ; loop until a random number less than 5 is generated + call Random + and a,7 + cp a,5 + jr nc,.randomLoop + inc a ; increment the random number, giving a range from 1 to 5 inclusive + ld b,a + ld a,[hl] + add b ; increase bait factor (for bait), increase escape factor (for rock) + jr nc,.noCarry + ld a,$ff +.noCarry + ld [hl],a + predef MoveAnimation ; do animation + ld c,70 + jp DelayFrames + +ThrewBaitText: + TX_FAR _ThrewBaitText + db "@" + +ThrewRockText: + TX_FAR _ThrewRockText + db "@" + +; also used for Dig out-of-battle effect +ItemUseEscapeRope: + ld a,[wIsInBattle] + and a + jr nz,.notUsable + ld a,[wCurMap] + cp a,AGATHAS_ROOM + jr z,.notUsable + ld a,[wCurMapTileset] + ld b,a + ld hl,EscapeRopeTilesets +.loop + ld a,[hli] + cp a,$ff + jr z,.notUsable + cp b + jr nz,.loop + ld hl,wd732 + set 3,[hl] + set 6,[hl] + ld hl,wd72e + res 4,[hl] + ResetEvent EVENT_IN_SAFARI_ZONE + xor a + ld [wNumSafariBalls],a + ld [wSafariZoneEntranceCurScript],a + inc a + ld [wEscapedFromBattle],a + ld [wActionResultOrTookBattleTurn],a ; item used + ld a,[wPseudoItemID] + and a ; using Dig? + ret nz ; if so, return + call ItemUseReloadOverworldData + ld c,30 + call DelayFrames + jp RemoveUsedItem +.notUsable + jp ItemUseNotTime + +EscapeRopeTilesets: + db FOREST, CEMETERY, CAVERN, FACILITY, INTERIOR + db $ff ; terminator + +ItemUseRepel: + ld b,100 + +ItemUseRepelCommon: + ld a,[wIsInBattle] + and a + jp nz,ItemUseNotTime + ld a,b + ld [wRepelRemainingSteps],a + jp PrintItemUseTextAndRemoveItem + +; handles X Accuracy item +ItemUseXAccuracy: + ld a,[wIsInBattle] + and a + jp z,ItemUseNotTime + ld hl,wPlayerBattleStatus2 + set USING_X_ACCURACY,[hl] ; X Accuracy bit + jp PrintItemUseTextAndRemoveItem + +; This function is bugged and never works. It always jumps to ItemUseNotTime. +; The Card Key is handled in a different way. +ItemUseCardKey: + xor a + ld [wUnusedD71F],a + call GetTileAndCoordsInFrontOfPlayer + ld a,[GetTileAndCoordsInFrontOfPlayer] + cp a,$18 + jr nz,.next0 + ld hl,CardKeyTable1 + jr .next1 +.next0 + cp a,$24 + jr nz,.next2 + ld hl,CardKeyTable2 + jr .next1 +.next2 + cp a,$5e + jp nz,ItemUseNotTime + ld hl,CardKeyTable3 +.next1 + ld a,[wCurMap] + ld b,a +.loop + ld a,[hli] + cp a,$ff + jp z,ItemUseNotTime + cp b + jr nz,.nextEntry1 + ld a,[hli] + cp d + jr nz,.nextEntry2 + ld a,[hli] + cp e + jr nz,.nextEntry3 + ld a,[hl] + ld [wUnusedD71F],a + jr .done +.nextEntry1 + inc hl +.nextEntry2 + inc hl +.nextEntry3 + inc hl + jr .loop +.done + ld hl,ItemUseText00 + call PrintText + ld hl,wd728 + set 7,[hl] + ret + +; These tables are probably supposed to be door locations in Silph Co., +; but they are unused. +; The reason there are 3 tables is unknown. + +; Format: +; 00: Map ID +; 01: Y +; 02: X +; 03: ID? + +CardKeyTable1: + db SILPH_CO_2F,$04,$04,$00 + db SILPH_CO_2F,$04,$05,$01 + db SILPH_CO_4F,$0C,$04,$02 + db SILPH_CO_4F,$0C,$05,$03 + db SILPH_CO_7F,$06,$0A,$04 + db SILPH_CO_7F,$06,$0B,$05 + db SILPH_CO_9F,$04,$12,$06 + db SILPH_CO_9F,$04,$13,$07 + db SILPH_CO_10F,$08,$0A,$08 + db SILPH_CO_10F,$08,$0B,$09 + db $ff + +CardKeyTable2: + db SILPH_CO_3F,$08,$09,$0A + db SILPH_CO_3F,$09,$09,$0B + db SILPH_CO_5F,$04,$07,$0C + db SILPH_CO_5F,$05,$07,$0D + db SILPH_CO_6F,$0C,$05,$0E + db SILPH_CO_6F,$0D,$05,$0F + db SILPH_CO_8F,$08,$07,$10 + db SILPH_CO_8F,$09,$07,$11 + db SILPH_CO_9F,$08,$03,$12 + db SILPH_CO_9F,$09,$03,$13 + db $ff + +CardKeyTable3: + db SILPH_CO_11F,$08,$09,$14 + db SILPH_CO_11F,$09,$09,$15 + db $ff + +ItemUsePokedoll: + ld a,[wIsInBattle] + dec a + jp nz,ItemUseNotTime + ld a,$01 + ld [wEscapedFromBattle],a + jp PrintItemUseTextAndRemoveItem + +ItemUseGuardSpec: + ld a,[wIsInBattle] + and a + jp z,ItemUseNotTime + ld hl,wPlayerBattleStatus2 + set PROTECTED_BY_MIST,[hl] ; Mist bit + jp PrintItemUseTextAndRemoveItem + +ItemUseSuperRepel: + ld b,200 + jp ItemUseRepelCommon + +ItemUseMaxRepel: + ld b,250 + jp ItemUseRepelCommon + +ItemUseDireHit: + ld a,[wIsInBattle] + and a + jp z,ItemUseNotTime + ld hl,wPlayerBattleStatus2 + set GETTING_PUMPED,[hl] ; Focus Energy bit + jp PrintItemUseTextAndRemoveItem + +ItemUseXStat: + ld a,[wIsInBattle] + and a + jr nz,.inBattle + call ItemUseNotTime + ld a,2 + ld [wActionResultOrTookBattleTurn],a ; item not used + ret +.inBattle + ld hl,wPlayerMoveNum + ld a,[hli] + push af ; save [wPlayerMoveNum] + ld a,[hl] + push af ; save [wPlayerMoveEffect] + push hl + ld a,[wcf91] + sub a,X_ATTACK - ATTACK_UP1_EFFECT + ld [hl],a ; store player move effect + call PrintItemUseTextAndRemoveItem + ld a,XSTATITEM_ANIM ; X stat item animation ID + ld [wPlayerMoveNum],a + call LoadScreenTilesFromBuffer1 ; restore saved screen + call Delay3 + xor a + ld [H_WHOSETURN],a ; set turn to player's turn + callba StatModifierUpEffect ; do stat increase move + pop hl + pop af + ld [hld],a ; restore [wPlayerMoveEffect] + pop af + ld [hl],a ; restore [wPlayerMoveNum] + ret + +ItemUsePokeflute: + ld a,[wIsInBattle] + and a + jr nz,.inBattle +; if not in battle + call ItemUseReloadOverworldData + ld a,[wCurMap] + cp a,ROUTE_12 + jr nz,.notRoute12 + CheckEvent EVENT_BEAT_ROUTE12_SNORLAX + jr nz,.noSnorlaxToWakeUp +; if the player hasn't beaten Route 12 Snorlax + ld hl,Route12SnorlaxFluteCoords + call ArePlayerCoordsInArray + jr nc,.noSnorlaxToWakeUp + ld hl,PlayedFluteHadEffectText + call PrintText + SetEvent EVENT_FIGHT_ROUTE12_SNORLAX + ret +.notRoute12 + cp a,ROUTE_16 + jr nz,.noSnorlaxToWakeUp + CheckEvent EVENT_BEAT_ROUTE16_SNORLAX + jr nz,.noSnorlaxToWakeUp +; if the player hasn't beaten Route 16 Snorlax + ld hl,Route16SnorlaxFluteCoords + call ArePlayerCoordsInArray + jr nc,.noSnorlaxToWakeUp + ld hl,PlayedFluteHadEffectText + call PrintText + SetEvent EVENT_FIGHT_ROUTE16_SNORLAX + ret +.noSnorlaxToWakeUp + ld hl,PlayedFluteNoEffectText + jp PrintText +.inBattle + xor a + ld [wWereAnyMonsAsleep],a + ld b,~SLP & $ff + ld hl,wPartyMon1Status + call WakeUpEntireParty + ld a,[wIsInBattle] + dec a ; is it a trainer battle? + jr z,.skipWakingUpEnemyParty +; if it's a trainer battle + ld hl,wEnemyMon1Status + call WakeUpEntireParty +.skipWakingUpEnemyParty + ld hl,wBattleMonStatus + ld a,[hl] + and b ; remove Sleep status + ld [hl],a + ld hl,wEnemyMonStatus + ld a,[hl] + and b ; remove Sleep status + ld [hl],a + call LoadScreenTilesFromBuffer2 ; restore saved screen + ld a,[wWereAnyMonsAsleep] + and a ; were any pokemon asleep before playing the flute? + ld hl,PlayedFluteNoEffectText + jp z,PrintText ; if no pokemon were asleep +; if some pokemon were asleep + ld hl,PlayedFluteHadEffectText + call PrintText + ld a,[wLowHealthAlarm] + and a,$80 + jr nz,.skipMusic + call WaitForSoundToFinish ; wait for sound to end + callba Music_PokeFluteInBattle ; play in-battle pokeflute music +.musicWaitLoop ; wait for music to finish playing + ld a,[wChannelSoundIDs + Ch6] + and a ; music off? + jr nz,.musicWaitLoop +.skipMusic + ld hl,FluteWokeUpText + jp PrintText + +; wakes up all party pokemon +; INPUT: +; hl must point to status of first pokemon in party (player's or enemy's) +; b must equal ~SLP +; [wWereAnyMonsAsleep] should be initialized to 0 +; OUTPUT: +; [wWereAnyMonsAsleep]: set to 1 if any pokemon were asleep +WakeUpEntireParty: + ld de,44 + ld c,6 +.loop + ld a,[hl] + push af + and a,SLP ; is pokemon asleep? + jr z,.notAsleep + ld a,1 + ld [wWereAnyMonsAsleep],a ; indicate that a pokemon had to be woken up +.notAsleep + pop af + and b ; remove Sleep status + ld [hl],a + add hl,de + dec c + jr nz,.loop + ret + +; Format: +; 00: Y +; 01: X +Route12SnorlaxFluteCoords: + db 62,9 ; one space West of Snorlax + db 61,10 ; one space North of Snorlax + db 63,10 ; one space South of Snorlax + db 62,11 ; one space East of Snorlax + db $ff ; terminator + +; Format: +; 00: Y +; 01: X +Route16SnorlaxFluteCoords: + db 10,27 ; one space East of Snorlax + db 10,25 ; one space West of Snorlax + db $ff ; terminator + +PlayedFluteNoEffectText: + TX_FAR _PlayedFluteNoEffectText + db "@" + +FluteWokeUpText: + TX_FAR _FluteWokeUpText + db "@" + +PlayedFluteHadEffectText: + TX_FAR _PlayedFluteHadEffectText + TX_BLINK + TX_ASM + ld a,[wIsInBattle] + and a + jr nz,.done +; play out-of-battle pokeflute music + ld a,$ff + call PlaySound ; turn off music + ld a, SFX_POKEFLUTE + ld c, BANK(SFX_Pokeflute) + call PlayMusic +.musicWaitLoop ; wait for music to finish playing + ld a,[wChannelSoundIDs + Ch2] + cp a, SFX_POKEFLUTE + jr z,.musicWaitLoop + call PlayDefaultMusic ; start playing normal music again +.done + jp TextScriptEnd ; end text + +ItemUseCoinCase: + ld a,[wIsInBattle] + and a + jp nz,ItemUseNotTime + ld hl,CoinCaseNumCoinsText + jp PrintText + +CoinCaseNumCoinsText: + TX_FAR _CoinCaseNumCoinsText + db "@" + +ItemUseOldRod: + call FishingInit + jp c, ItemUseNotTime + lb bc, 5, MAGIKARP + ld a, $1 ; set bite + jr RodResponse + +ItemUseGoodRod: + call FishingInit + jp c,ItemUseNotTime +.RandomLoop + call Random + srl a + jr c, .SetBite + and %11 + cp 2 + jr nc, .RandomLoop + ; choose which monster appears + ld hl,GoodRodMons + add a,a + ld c,a + ld b,0 + add hl,bc + ld b,[hl] + inc hl + ld c,[hl] + and a +.SetBite + ld a,0 + rla + xor 1 + jr RodResponse + +INCLUDE "data/good_rod.asm" + +ItemUseSuperRod: + call FishingInit + jp c, ItemUseNotTime + call ReadSuperRodData + ld a, e +RodResponse: + ld [wRodResponse], a + + dec a ; is there a bite? + jr nz, .next + ; if yes, store level and species data + ld a, 1 + ld [wMoveMissed], a + ld a, b ; level + ld [wCurEnemyLVL], a + ld a, c ; species + ld [wCurOpponent], a + +.next + ld hl, wWalkBikeSurfState + ld a, [hl] ; store the value in a + push af + push hl + ld [hl], 0 + callba FishingAnim + pop hl + pop af + ld [hl], a + ret + +; checks if fishing is possible and if so, runs initialization code common to all rods +; unsets carry if fishing is possible, sets carry if not +FishingInit: + ld a,[wIsInBattle] + and a + jr z,.notInBattle + scf ; can't fish during battle + ret +.notInBattle + call IsNextTileShoreOrWater + ret c + ld a,[wWalkBikeSurfState] + cp a,2 ; Surfing? + jr z,.surfing + call ItemUseReloadOverworldData + ld hl,ItemUseText00 + call PrintText + ld a,SFX_HEAL_AILMENT + call PlaySound + ld c,80 + call DelayFrames + and a + ret +.surfing + scf ; can't fish when surfing + ret + +ItemUseOaksParcel: + jp ItemUseNotYoursToUse + +ItemUseItemfinder: + ld a,[wIsInBattle] + and a + jp nz,ItemUseNotTime + call ItemUseReloadOverworldData + callba HiddenItemNear ; check for hidden items + ld hl,ItemfinderFoundNothingText + jr nc,.printText ; if no hidden items + ld c,4 +.loop + ld a,SFX_HEALING_MACHINE + call PlaySoundWaitForCurrent + ld a,SFX_PURCHASE + call PlaySoundWaitForCurrent + dec c + jr nz,.loop + ld hl,ItemfinderFoundItemText +.printText + jp PrintText + +ItemfinderFoundItemText: + TX_FAR _ItemfinderFoundItemText + db "@" + +ItemfinderFoundNothingText: + TX_FAR _ItemfinderFoundNothingText + db "@" + +ItemUsePPUp: + ld a,[wIsInBattle] + and a + jp nz,ItemUseNotTime + +ItemUsePPRestore: + ld a,[wWhichPokemon] + push af + ld a,[wcf91] + ld [wPPRestoreItem],a +.chooseMon + xor a + ld [wUpdateSpritesEnabled],a + ld a,USE_ITEM_PARTY_MENU + ld [wPartyMenuTypeOrMessageID],a + call DisplayPartyMenu + jr nc,.chooseMove + jp .itemNotUsed +.chooseMove + ld a,[wPPRestoreItem] + cp a,ELIXER + jp nc,.useElixir ; if Elixir or Max Elixir + ld a,$02 + ld [wMoveMenuType],a + ld hl,RaisePPWhichTechniqueText + ld a,[wPPRestoreItem] + cp a,ETHER ; is it a PP Up? + jr c,.printWhichTechniqueMessage ; if so, print the raise PP message + ld hl,RestorePPWhichTechniqueText ; otherwise, print the restore PP message +.printWhichTechniqueMessage + call PrintText + xor a + ld [wPlayerMoveListIndex],a + callab MoveSelectionMenu ; move selection menu + ld a,0 + ld [wPlayerMoveListIndex],a + jr nz,.chooseMon + ld hl,wPartyMon1Moves + ld bc, wPartyMon2 - wPartyMon1 + call GetSelectedMoveOffset + push hl + ld a,[hl] + ld [wd11e],a + call GetMoveName + call CopyStringToCF50 ; copy name to wcf50 + pop hl + ld a,[wPPRestoreItem] + cp a,ETHER + jr nc,.useEther ; if Ether or Max Ether +.usePPUp + ld bc,wPartyMon1PP - wPartyMon1Moves + add hl,bc + ld a,[hl] ; move PP + cp a,3 << 6 ; have 3 PP Ups already been used? + jr c,.PPNotMaxedOut + ld hl,PPMaxedOutText + call PrintText + jr .chooseMove +.PPNotMaxedOut + ld a,[hl] + add a,1 << 6 ; increase PP Up count by 1 + ld [hl],a + ld a,1 ; 1 PP Up used + ld [wd11e],a + call RestoreBonusPP ; add the bonus PP to current PP + ld hl,PPIncreasedText + call PrintText +.done + pop af + ld [wWhichPokemon],a + call GBPalWhiteOut + call RunDefaultPaletteCommand + jp RemoveUsedItem +.afterRestoringPP ; after using a (Max) Ether/Elixir + ld a,[wWhichPokemon] + ld b,a + ld a,[wPlayerMonNumber] + cp b ; is the pokemon whose PP was restored active in battle? + jr nz,.skipUpdatingInBattleData + ld hl,wPartyMon1PP + ld bc, wPartyMon2 - wPartyMon1 + call AddNTimes + ld de,wBattleMonPP + ld bc,4 + call CopyData ; copy party data to in-battle data +.skipUpdatingInBattleData + ld a,SFX_HEAL_AILMENT + call PlaySound + ld hl,PPRestoredText + call PrintText + jr .done +.useEther + call .restorePP + jr nz,.afterRestoringPP + jp .noEffect +; unsets zero flag if PP was restored, sets zero flag if not +; however, this is bugged for Max Ethers and Max Elixirs (see below) +.restorePP + xor a ; PLAYER_PARTY_DATA + ld [wMonDataLocation],a + call GetMaxPP + ld hl,wPartyMon1Moves + ld bc, wPartyMon2 - wPartyMon1 + call GetSelectedMoveOffset + ld bc, wPartyMon1PP - wPartyMon1Moves + add hl,bc ; hl now points to move's PP + ld a,[wMaxPP] + ld b,a + ld a,[wPPRestoreItem] + cp a,MAX_ETHER + jr z,.fullyRestorePP + ld a,[hl] ; move PP + and a,%00111111 ; lower 6 bit bits store current PP + cp b ; does current PP equal max PP? + ret z ; if so, return + add a,10 ; increase current PP by 10 +; b holds the max PP amount and b will hold the new PP amount. +; So, if the new amount meets or exceeds the max amount, +; cap the amount to the max amount by leaving b unchanged. +; Otherwise, store the new amount in b. + cp b ; does the new amount meet or exceed the maximum? + jr nc,.storeNewAmount + ld b,a +.storeNewAmount + ld a,[hl] ; move PP + and a,%11000000 ; PP Up counter bits + add b + ld [hl],a + ret +.fullyRestorePP + ld a,[hl] ; move PP +; Note that this code has a bug. It doesn't mask out the upper two bits, which +; are used to count how many PP Ups have been used on the move. So, Max Ethers +; and Max Elixirs will not be detected as having no effect on a move with full +; PP if the move has had any PP Ups used on it. + cp b ; does current PP equal max PP? + ret z + jr .storeNewAmount +.useElixir +; decrement the item ID so that ELIXER becomes ETHER and MAX_ELIXER becomes MAX_ETHER + ld hl,wPPRestoreItem + dec [hl] + dec [hl] + xor a + ld hl,wCurrentMenuItem + ld [hli],a + ld [hl],a ; zero the counter for number of moves that had their PP restored + ld b,4 +; loop through each move and restore PP +.elixirLoop + push bc + ld hl,wPartyMon1Moves + ld bc, wPartyMon2 - wPartyMon1 + call GetSelectedMoveOffset + ld a,[hl] + and a ; does the current slot have a move? + jr z,.nextMove + call .restorePP + jr z,.nextMove +; if some PP was restored + ld hl,wTileBehindCursor ; counter for number of moves that had their PP restored + inc [hl] +.nextMove + ld hl,wCurrentMenuItem + inc [hl] + pop bc + dec b + jr nz,.elixirLoop + ld a,[wTileBehindCursor] + and a ; did any moves have their PP restored? + jp nz,.afterRestoringPP +.noEffect + call ItemUseNoEffect +.itemNotUsed + call GBPalWhiteOut + call RunDefaultPaletteCommand + pop af + xor a + ld [wActionResultOrTookBattleTurn],a ; item use failed + ret + +RaisePPWhichTechniqueText: + TX_FAR _RaisePPWhichTechniqueText + db "@" + +RestorePPWhichTechniqueText: + TX_FAR _RestorePPWhichTechniqueText + db "@" + +PPMaxedOutText: + TX_FAR _PPMaxedOutText + db "@" + +PPIncreasedText: + TX_FAR _PPIncreasedText + db "@" + +PPRestoredText: + TX_FAR _PPRestoredText + db "@" + +; for items that can't be used from the Item menu +UnusableItem: + jp ItemUseNotTime + +ItemUseTMHM: + ld a,[wIsInBattle] + and a + jp nz,ItemUseNotTime + ld a,[wcf91] + sub a,TM_01 + push af + jr nc,.skipAdding + add a,55 ; if item is an HM, add 55 +.skipAdding + inc a + ld [wd11e],a + predef TMToMove ; get move ID from TM/HM ID + ld a,[wd11e] + ld [wMoveNum],a + call GetMoveName + call CopyStringToCF50 ; copy name to wcf50 + pop af + ld hl,BootedUpTMText + jr nc,.printBootedUpMachineText + ld hl,BootedUpHMText +.printBootedUpMachineText + call PrintText + ld hl,TeachMachineMoveText + call PrintText + coord hl, 13, 7 + lb bc, 8, 14 + ld a,TWO_OPTION_MENU + ld [wTextBoxID],a + call DisplayTextBoxID ; yes/no menu + ld a,[wCurrentMenuItem] + and a + jr z,.useMachine + ld a,2 + ld [wActionResultOrTookBattleTurn],a ; item not used + ret +.useMachine + ld a,[wWhichPokemon] + push af + ld a,[wcf91] + push af +.chooseMon + ld hl,wcf50 + ld de,wTempMoveNameBuffer + ld bc,14 + call CopyData ; save the move name because DisplayPartyMenu will overwrite it + ld a,$ff + ld [wUpdateSpritesEnabled],a + ld a,TMHM_PARTY_MENU + ld [wPartyMenuTypeOrMessageID],a + call DisplayPartyMenu + push af + ld hl,wTempMoveNameBuffer + ld de,wcf50 + ld bc,14 + call CopyData + pop af + jr nc,.checkIfAbleToLearnMove +; if the player canceled teaching the move + pop af + pop af + call GBPalWhiteOutWithDelay3 + call ClearSprites + call RunDefaultPaletteCommand + jp LoadScreenTilesFromBuffer1 ; restore saved screen +.checkIfAbleToLearnMove + predef CanLearnTM ; check if the pokemon can learn the move + push bc + ld a,[wWhichPokemon] + ld hl,wPartyMonNicks + call GetPartyMonName + pop bc + ld a,c + and a ; can the pokemon learn the move? + jr nz,.checkIfAlreadyLearnedMove +; if the pokemon can't learn the move + ld a,SFX_DENIED + call PlaySoundWaitForCurrent + ld hl,MonCannotLearnMachineMoveText + call PrintText + jr .chooseMon +.checkIfAlreadyLearnedMove + callab CheckIfMoveIsKnown ; check if the pokemon already knows the move + jr c,.chooseMon + predef LearnMove ; teach move + pop af + ld [wcf91],a + pop af + ld [wWhichPokemon],a + ld a,b + and a + ret z + ld a,[wcf91] + call IsItemHM + ret c + jp RemoveUsedItem + +BootedUpTMText: + TX_FAR _BootedUpTMText + db "@" + +BootedUpHMText: + TX_FAR _BootedUpHMText + db "@" + +TeachMachineMoveText: + TX_FAR _TeachMachineMoveText + db "@" + +MonCannotLearnMachineMoveText: + TX_FAR _MonCannotLearnMachineMoveText + db "@" + +PrintItemUseTextAndRemoveItem: + ld hl,ItemUseText00 + call PrintText + ld a,SFX_HEAL_AILMENT + call PlaySound + call WaitForTextScrollButtonPress ; wait for button press + +RemoveUsedItem: + ld hl,wNumBagItems + ld a,1 ; one item + ld [wItemQuantity],a + jp RemoveItemFromInventory + +ItemUseNoEffect: + ld hl,ItemUseNoEffectText + jr ItemUseFailed + +ItemUseNotTime: + ld hl,ItemUseNotTimeText + jr ItemUseFailed + +ItemUseNotYoursToUse: + ld hl,ItemUseNotYoursToUseText + jr ItemUseFailed + +ThrowBallAtTrainerMon: + call RunDefaultPaletteCommand + call LoadScreenTilesFromBuffer1 ; restore saved screen + call Delay3 + ld a,TOSS_ANIM + ld [wAnimationID],a + predef MoveAnimation ; do animation + ld hl,ThrowBallAtTrainerMonText1 + call PrintText + ld hl,ThrowBallAtTrainerMonText2 + call PrintText + jr RemoveUsedItem + +NoCyclingAllowedHere: + ld hl,NoCyclingAllowedHereText + jr ItemUseFailed + +BoxFullCannotThrowBall: + ld hl,BoxFullCannotThrowBallText + jr ItemUseFailed + +SurfingAttemptFailed: + ld hl,NoSurfingHereText + +ItemUseFailed: + xor a + ld [wActionResultOrTookBattleTurn],a ; item use failed + jp PrintText + +ItemUseNotTimeText: + TX_FAR _ItemUseNotTimeText + db "@" + +ItemUseNotYoursToUseText: + TX_FAR _ItemUseNotYoursToUseText + db "@" + +ItemUseNoEffectText: + TX_FAR _ItemUseNoEffectText + db "@" + +ThrowBallAtTrainerMonText1: + TX_FAR _ThrowBallAtTrainerMonText1 + db "@" + +ThrowBallAtTrainerMonText2: + TX_FAR _ThrowBallAtTrainerMonText2 + db "@" + +NoCyclingAllowedHereText: + TX_FAR _NoCyclingAllowedHereText + db "@" + +NoSurfingHereText: + TX_FAR _NoSurfingHereText + db "@" + +BoxFullCannotThrowBallText: + TX_FAR _BoxFullCannotThrowBallText + db "@" + +ItemUseText00: + TX_FAR _ItemUseText001 + TX_LINE + TX_FAR _ItemUseText002 + db "@" + +GotOnBicycleText: + TX_FAR _GotOnBicycleText1 + TX_LINE + TX_FAR _GotOnBicycleText2 + db "@" + +GotOffBicycleText: + TX_FAR _GotOffBicycleText1 + TX_LINE + TX_FAR _GotOffBicycleText2 + db "@" + +; restores bonus PP (from PP Ups) when healing at a pokemon center +; also, when a PP Up is used, it increases the current PP by one PP Up bonus +; INPUT: +; [wWhichPokemon] = index of pokemon in party +; [wCurrentMenuItem] = index of move (when using a PP Up) +RestoreBonusPP: + ld hl,wPartyMon1Moves + ld bc, wPartyMon2 - wPartyMon1 + ld a,[wWhichPokemon] + call AddNTimes + push hl + ld de,wNormalMaxPPList - 1 + predef LoadMovePPs ; loads the normal max PP of each of the pokemon's moves to wNormalMaxPPList + pop hl + ld c, wPartyMon1PP - wPartyMon1Moves + ld b,0 + add hl,bc ; hl now points to move 1 PP + ld de,wNormalMaxPPList + ld b,0 ; initialize move counter to zero +; loop through the pokemon's moves +.loop + inc b + ld a,b + cp a,5 ; reached the end of the pokemon's moves? + ret z ; if so, return + ld a,[wUsingPPUp] + dec a ; using a PP Up? + jr nz,.skipMenuItemIDCheck +; if using a PP Up, check if this is the move it's being used on + ld a,[wCurrentMenuItem] + inc a + cp b + jr nz,.nextMove +.skipMenuItemIDCheck + ld a,[hl] + and a,%11000000 ; have any PP Ups been used? + call nz,AddBonusPP ; if so, add bonus PP +.nextMove + inc hl + inc de + jr .loop + +; adds bonus PP from PP Ups to current PP +; 1/5 of normal max PP (capped at 7) is added for each PP Up +; INPUT: +; [de] = normal max PP +; [hl] = move PP +AddBonusPP: + push bc + ld a,[de] ; normal max PP of move + ld [H_DIVIDEND + 3],a + xor a + ld [H_DIVIDEND],a + ld [H_DIVIDEND + 1],a + ld [H_DIVIDEND + 2],a + ld a,5 + ld [H_DIVISOR],a + ld b,4 + call Divide + ld a,[hl] ; move PP + ld b,a + swap a + and a,%00001111 + srl a + srl a + ld c,a ; c = number of PP Ups used +.loop + ld a,[H_QUOTIENT + 3] + cp a,8 ; is the amount greater than or equal to 8? + jr c,.addAmount + ld a,7 ; cap the amount at 7 +.addAmount + add b + ld b,a + ld a,[wUsingPPUp] + dec a ; is the player using a PP Up right now? + jr z,.done ; if so, only add the bonus once + dec c + jr nz,.loop +.done + ld [hl],b + pop bc + ret + +; gets max PP of a pokemon's move (including PP from PP Ups) +; INPUT: +; [wWhichPokemon] = index of pokemon within party/box +; [wMonDataLocation] = pokemon source +; 00: player's party +; 01: enemy's party +; 02: current box +; 03: daycare +; 04: player's in-battle pokemon +; [wCurrentMenuItem] = move index +; OUTPUT: +; [wMaxPP] = max PP +GetMaxPP: + ld a,[wMonDataLocation] + and a + ld hl,wPartyMon1Moves + ld bc,wPartyMon2 - wPartyMon1 + jr z,.sourceWithMultipleMon + ld hl,wEnemyMon1Moves + dec a + jr z,.sourceWithMultipleMon + ld hl,wBoxMon1Moves + ld bc,wBoxMon2 - wBoxMon1 + dec a + jr z,.sourceWithMultipleMon + ld hl,wDayCareMonMoves + dec a + jr z,.sourceWithOneMon + ld hl,wBattleMonMoves ; player's in-battle pokemon +.sourceWithOneMon + call GetSelectedMoveOffset2 + jr .next +.sourceWithMultipleMon + call GetSelectedMoveOffset +.next + ld a,[hl] + dec a + push hl + ld hl,Moves + ld bc,MoveEnd - Moves + call AddNTimes + ld de,wcd6d + ld a,BANK(Moves) + call FarCopyData + ld de,wcd6d + 5 ; PP is byte 5 of move data + ld a,[de] + ld b,a ; b = normal max PP + pop hl + push bc + ld bc,wPartyMon1PP - wPartyMon1Moves ; PP offset if not player's in-battle pokemon data + ld a,[wMonDataLocation] + cp a,4 ; player's in-battle pokemon? + jr nz,.addPPOffset + ld bc,wBattleMonPP - wBattleMonMoves ; PP offset if player's in-battle pokemon data +.addPPOffset + add hl,bc + ld a,[hl] ; a = current PP + and a,%11000000 ; get PP Up count + pop bc + or b ; place normal max PP in 6 lower bits of a + ld h,d + ld l,e + inc hl ; hl = wcd73 + ld [hl],a + xor a ; add the bonus for the existing PP Up count + ld [wUsingPPUp],a + call AddBonusPP ; add bonus PP from PP Ups + ld a,[hl] + and a,%00111111 ; mask out the PP Up count + ld [wMaxPP],a ; store max PP + ret + +GetSelectedMoveOffset: + ld a,[wWhichPokemon] + call AddNTimes + +GetSelectedMoveOffset2: + ld a,[wCurrentMenuItem] + ld c,a + ld b,0 + add hl,bc + ret + +; confirms the item toss and then tosses the item +; INPUT: +; hl = address of inventory (either wNumBagItems or wNumBoxItems) +; [wcf91] = item ID +; [wWhichPokemon] = index of item within inventory +; [wItemQuantity] = quantity to toss +; OUTPUT: +; clears carry flag if the item is tossed, sets carry flag if not +TossItem_: + push hl + ld a,[wcf91] + call IsItemHM + pop hl + jr c,.tooImportantToToss + push hl + call IsKeyItem_ + ld a,[wIsKeyItem] + pop hl + and a + jr nz,.tooImportantToToss + push hl + ld a,[wcf91] + ld [wd11e],a + call GetItemName + call CopyStringToCF50 ; copy name to wcf50 + ld hl,IsItOKToTossItemText + call PrintText + coord hl, 13, 7 + lb bc, 8, 14 + ld a,TWO_OPTION_MENU + ld [wTextBoxID],a + call DisplayTextBoxID ; yes/no menu + ld a,[wMenuExitMethod] + cp a,CHOSE_SECOND_ITEM + pop hl + scf + ret z ; return if the player chose No +; if the player chose Yes + push hl + ld a,[wWhichPokemon] + call RemoveItemFromInventory + ld a,[wcf91] + ld [wd11e],a + call GetItemName + call CopyStringToCF50 ; copy name to wcf50 + ld hl,ThrewAwayItemText + call PrintText + pop hl + and a + ret +.tooImportantToToss + push hl + ld hl,TooImportantToTossText + call PrintText + pop hl + scf + ret + +ThrewAwayItemText: + TX_FAR _ThrewAwayItemText + db "@" + +IsItOKToTossItemText: + TX_FAR _IsItOKToTossItemText + db "@" + +TooImportantToTossText: + TX_FAR _TooImportantToTossText + db "@" + +; checks if an item is a key item +; INPUT: +; [wcf91] = item ID +; OUTPUT: +; [wIsKeyItem] = result +; 00: item is not key item +; 01: item is key item +IsKeyItem_: + ld a,$01 + ld [wIsKeyItem],a + ld a,[wcf91] + cp a,HM_01 ; is the item an HM or TM? + jr nc,.checkIfItemIsHM +; if the item is not an HM or TM + push af + ld hl,KeyItemBitfield + ld de,wBuffer + ld bc,15 ; only 11 bytes are actually used + call CopyData + pop af + dec a + ld c,a + ld hl,wBuffer + ld b,FLAG_TEST + predef FlagActionPredef + ld a,c + and a + ret nz +.checkIfItemIsHM + ld a,[wcf91] + call IsItemHM + ret c + xor a + ld [wIsKeyItem],a + ret + +INCLUDE "data/key_items.asm" + +SendNewMonToBox: + ld de, wNumInBox + ld a, [de] + inc a + ld [de], a + ld a, [wcf91] + ld [wd0b5], a + ld c, a +.asm_e7b1 + inc de + ld a, [de] + ld b, a + ld a, c + ld c, b + ld [de], a + cp $ff + jr nz, .asm_e7b1 + call GetMonHeader + ld hl, wBoxMonOT + ld bc, NAME_LENGTH + ld a, [wNumInBox] + dec a + jr z, .asm_e7ee + dec a + call AddNTimes + push hl + ld bc, NAME_LENGTH + add hl, bc + ld d, h + ld e, l + pop hl + ld a, [wNumInBox] + dec a + ld b, a +.asm_e7db + push bc + push hl + ld bc, NAME_LENGTH + call CopyData + pop hl + ld d, h + ld e, l + ld bc, -NAME_LENGTH + add hl, bc + pop bc + dec b + jr nz, .asm_e7db +.asm_e7ee + ld hl, wPlayerName + ld de, wBoxMonOT + ld bc, NAME_LENGTH + call CopyData + ld a, [wNumInBox] + dec a + jr z, .asm_e82a + ld hl, wBoxMonNicks + ld bc, NAME_LENGTH + dec a + call AddNTimes + push hl + ld bc, NAME_LENGTH + add hl, bc + ld d, h + ld e, l + pop hl + ld a, [wNumInBox] + dec a + ld b, a +.asm_e817 + push bc + push hl + ld bc, NAME_LENGTH + call CopyData + pop hl + ld d, h + ld e, l + ld bc, -NAME_LENGTH + add hl, bc + pop bc + dec b + jr nz, .asm_e817 +.asm_e82a + ld hl, wBoxMonNicks + ld a, NAME_MON_SCREEN + ld [wNamingScreenType], a + predef AskName + ld a, [wNumInBox] + dec a + jr z, .asm_e867 + ld hl, wBoxMons + ld bc, wBoxMon2 - wBoxMon1 + dec a + call AddNTimes + push hl + ld bc, wBoxMon2 - wBoxMon1 + add hl, bc + ld d, h + ld e, l + pop hl + ld a, [wNumInBox] + dec a + ld b, a +.asm_e854 + push bc + push hl + ld bc, wBoxMon2 - wBoxMon1 + call CopyData + pop hl + ld d, h + ld e, l + ld bc, wBoxMon1 - wBoxMon2 + add hl, bc + pop bc + dec b + jr nz, .asm_e854 +.asm_e867 + ld a, [wEnemyMonLevel] + ld [wEnemyMonBoxLevel], a + ld hl, wEnemyMon + ld de, wBoxMon1 + ld bc, wEnemyMonDVs - wEnemyMon + call CopyData + ld hl, wPlayerID + ld a, [hli] + ld [de], a + inc de + ld a, [hl] + ld [de], a + inc de + push de + ld a, [wCurEnemyLVL] + ld d, a + callab CalcExperience + pop de + ld a, [hExperience] + ld [de], a + inc de + ld a, [hExperience + 1] + ld [de], a + inc de + ld a, [hExperience + 2] + ld [de], a + inc de + xor a + ld b, NUM_STATS * 2 +.asm_e89f + ld [de], a + inc de + dec b + jr nz, .asm_e89f + ld hl, wEnemyMonDVs + ld a, [hli] + ld [de], a + inc de + ld a, [hli] + ld [de], a + ld hl, wEnemyMonPP + ld b, NUM_MOVES +.asm_e8b1 + ld a, [hli] + inc de + ld [de], a + dec b + jr nz, .asm_e8b1 + ret + +; checks if the tile in front of the player is a shore or water tile +; used for surfing and fishing +; unsets carry if it is, sets carry if not +IsNextTileShoreOrWater: + ld a, [wCurMapTileset] + ld hl, WaterTilesets + ld de,1 + call IsInArray + jr nc, .notShoreOrWater + ld a, [wCurMapTileset] + cp SHIP_PORT ; Vermilion Dock tileset + ld a, [wTileInFrontOfPlayer] ; tile in front of player + jr z, .skipShoreTiles ; if it's the Vermilion Dock tileset + cp $48 ; eastern shore tile in Safari Zone + jr z, .shoreOrWater + cp $32 ; usual eastern shore tile + jr z, .shoreOrWater +.skipShoreTiles + cp $14 ; water tile + jr z, .shoreOrWater +.notShoreOrWater + scf + ret +.shoreOrWater + and a + ret + +; tilesets with water +WaterTilesets: + db OVERWORLD, FOREST, DOJO, GYM, SHIP, SHIP_PORT, CAVERN, FACILITY, PLATEAU + db $ff ; terminator + +ReadSuperRodData: +; return e = 2 if no fish on this map +; return e = 1 if a bite, bc = level,species +; return e = 0 if no bite + ld a, [wCurMap] + ld de, 3 ; each fishing group is three bytes wide + ld hl, SuperRodData + call IsInArray + jr c, .ReadFishingGroup + ld e, $2 ; $2 if no fishing groups found + ret + +.ReadFishingGroup +; hl points to the fishing group entry in the index + inc hl ; skip map id + + ; read fishing group address + ld a, [hli] + ld h, [hl] + ld l, a + + ld b, [hl] ; how many mons in group + inc hl ; point to data + ld e, $0 ; no bite yet + +.RandomLoop + call Random + srl a + ret c ; 50% chance of no battle + + and %11 ; 2-bit random number + cp b + jr nc, .RandomLoop ; if a is greater than the number of mons, regenerate + + ; get the mon + add a + ld c, a + ld b, $0 + add hl, bc + ld b, [hl] ; level + inc hl + ld c, [hl] ; species + ld e, $1 ; $1 if there's a bite + ret + +INCLUDE "data/super_rod.asm" + +; reloads map view and processes sprite data +; for items that cause the overworld to be displayed +ItemUseReloadOverworldData: + call LoadCurrentMapView + jp UpdateSprites + +; creates a list at wBuffer of maps where the mon in [wd11e] can be found. +; this is used by the pokedex to display locations the mon can be found on the map. +FindWildLocationsOfMon: + ld hl, WildDataPointers + ld de, wBuffer + ld c, $0 +.loop + inc hl + ld a, [hld] + inc a + jr z, .done + push hl + ld a, [hli] + ld h, [hl] + ld l, a + ld a, [hli] + and a + call nz, CheckMapForMon ; land + ld a, [hli] + and a + call nz, CheckMapForMon ; water + pop hl + inc hl + inc hl + inc c + jr .loop +.done + ld a, $ff ; list terminator + ld [de], a + ret + +CheckMapForMon: + inc hl + ld b, $a +.loop + ld a, [wd11e] + cp [hl] + jr nz, .nextEntry + ld a, c + ld [de], a + inc de +.nextEntry + inc hl + inc hl + dec b + jr nz, .loop + dec hl + ret diff --git a/de/engine/learn_move.asm b/de/engine/learn_move.asm new file mode 100755 index 00000000..8dd72cd8 --- /dev/null +++ b/de/engine/learn_move.asm @@ -0,0 +1,226 @@ +LearnMove: + call SaveScreenTilesToBuffer1 + ld a, [wWhichPokemon] + ld hl, wPartyMonNicks + call GetPartyMonName + ld hl, wcd6d + ld de, wLearnMoveMonName + ld bc, NAME_LENGTH + call CopyData + +DontAbandonLearning: + ld hl, wPartyMon1Moves + ld bc, wPartyMon2Moves - wPartyMon1Moves + ld a, [wWhichPokemon] + call AddNTimes + ld d, h + ld e, l + ld b, NUM_MOVES +.findEmptyMoveSlotLoop + ld a, [hl] + and a + jr z, .next + inc hl + dec b + jr nz, .findEmptyMoveSlotLoop + push de + call TryingToLearn + pop de + jp c, AbandonLearning + push hl + push de + ld [wd11e], a + call GetMoveName + ld hl, OneTwoAndText + call PrintText + pop de + pop hl +.next + ld a, [wMoveNum] + ld [hl], a + ld bc, wPartyMon1PP - wPartyMon1Moves + add hl, bc + push hl + push de + dec a + ld hl, Moves + ld bc, MoveEnd - Moves + call AddNTimes + ld de, wBuffer + ld a, BANK(Moves) + call FarCopyData + ld a, [wBuffer + 5] ; a = move's max PP + pop de + pop hl + ld [hl], a + ld a, [wIsInBattle] + and a + jp z, PrintLearnedMove + ld a, [wWhichPokemon] + ld b, a + ld a, [wPlayerMonNumber] + cp b + jp nz, PrintLearnedMove + ld h, d + ld l, e + ld de, wBattleMonMoves + ld bc, NUM_MOVES + call CopyData + ld bc, wPartyMon1PP - wPartyMon1OTID + add hl, bc + ld de, wBattleMonPP + ld bc, NUM_MOVES + call CopyData + jp PrintLearnedMove + +AbandonLearning: + ld hl, AbandonLearningText + call PrintText + coord hl, 13, 7 + lb bc, 8, 14 + ld a, TWO_OPTION_MENU + ld [wTextBoxID], a + call DisplayTextBoxID ; yes/no menu + ld a, [wCurrentMenuItem] + and a + jp nz, DontAbandonLearning + ld hl, DidNotLearnText + call PrintText + ld b, 0 + ret + +PrintLearnedMove: + ld hl, LearnedMove1Text + call PrintText + ld b, 1 + ret + +TryingToLearn: + push hl + ld hl, TryingToLearnText + call PrintText + coord hl, 13, 7 + lb bc, 8, 14 + ld a, TWO_OPTION_MENU + ld [wTextBoxID], a + call DisplayTextBoxID ; yes/no menu + pop hl + ld a, [wCurrentMenuItem] + rra + ret c + ld bc, -NUM_MOVES + add hl, bc + push hl + ld de, wMoves + ld bc, NUM_MOVES + call CopyData + callab FormatMovesString + pop hl +.loop + push hl + ld hl, WhichMoveToForgetText + call PrintText + coord hl, 4, 7 + ld b, 4 + ld c, 14 + call TextBoxBorder + coord hl, 6, 8 + ld de, wMovesString + ld a, [hFlags_0xFFF6] + set 2, a + ld [hFlags_0xFFF6], a + call PlaceString + ld a, [hFlags_0xFFF6] + res 2, a + ld [hFlags_0xFFF6], a + ld hl, wTopMenuItemY + ld a, 8 + ld [hli], a ; wTopMenuItemY + ld a, 5 + ld [hli], a ; wTopMenuItemX + xor a + ld [hli], a ; wCurrentMenuItem + inc hl + ld a, [wNumMovesMinusOne] + ld [hli], a ; wMaxMenuItem + ld a, A_BUTTON | B_BUTTON + ld [hli], a ; wMenuWatchedKeys + ld [hl], 0 ; wLastMenuItem + ld hl, hFlags_0xFFF6 + set 1, [hl] + call HandleMenuInput + ld hl, hFlags_0xFFF6 + res 1, [hl] + push af + call LoadScreenTilesFromBuffer1 + pop af + pop hl + bit 1, a ; pressed b + jr nz, .cancel + push hl + ld a, [wCurrentMenuItem] + ld c, a + ld b, 0 + add hl, bc + ld a, [hl] + push af + push bc + call IsMoveHM + pop bc + pop de + ld a, d + jr c, .hm + pop hl + add hl, bc + and a + ret +.hm + ld hl, HMCantDeleteText + call PrintText + pop hl + jr .loop +.cancel + scf + ret + +LearnedMove1Text: + TX_FAR _LearnedMove1Text + TX_SFX_ITEM_1 ; plays SFX_GET_ITEM_1 in the party menu (rare candy) and plays SFX_LEVEL_UP in battle + TX_BLINK + db "@" + +WhichMoveToForgetText: + TX_FAR _WhichMoveToForgetText + db "@" + +AbandonLearningText: + TX_FAR _AbandonLearningText + db "@" + +DidNotLearnText: + TX_FAR _DidNotLearnText + db "@" + +TryingToLearnText: + TX_FAR _TryingToLearnText + db "@" + +OneTwoAndText: + TX_FAR _OneTwoAndText + TX_DELAY + TX_ASM + ld a, SFX_SWAP + call PlaySoundWaitForCurrent + ld hl, PoofText + ret + +PoofText: + TX_FAR _PoofText + TX_DELAY +ForgotAndText: + TX_FAR _ForgotAndText + db "@" + +HMCantDeleteText: + TX_FAR _HMCantDeleteText + db "@" diff --git a/de/engine/menu/bills_pc.asm b/de/engine/menu/bills_pc.asm new file mode 100644 index 00000000..50db8d92 --- /dev/null +++ b/de/engine/menu/bills_pc.asm @@ -0,0 +1,554 @@ +DisplayPCMainMenu:: + xor a + ld [H_AUTOBGTRANSFERENABLED], a + call SaveScreenTilesToBuffer2 + ld a, [wNumHoFTeams] + and a + jr nz, .leaguePCAvailable + CheckEvent EVENT_GOT_POKEDEX + jr z, .noOaksPC + ld a, [wNumHoFTeams] + and a + jr nz, .leaguePCAvailable + coord hl, 0, 0 + ld b, 8 + ld c, 15 + jr .next +.noOaksPC + coord hl, 0, 0 + ld b, 6 + ld c, 15 + jr .next +.leaguePCAvailable + coord hl, 0, 0 + ld b, 10 + ld c, 15 +.next + call TextBoxBorder + call UpdateSprites + ld a, 3 + ld [wMaxMenuItem], a + CheckEvent EVENT_MET_BILL + jr nz, .metBill + coord hl, 2, 2 + ld de, SomeonesPCText + jr .next2 +.metBill + coord hl, 2, 2 + ld de, BillsPCText +.next2 + call PlaceString + coord hl, 2, 4 + ld de, PlayersPCText + call PlaceString + ld l, c + ld h, b + ld de, wPlayerName + call PlaceString + CheckEvent EVENT_GOT_POKEDEX + jr z, .noOaksPC2 + coord hl, 2, 6 + ld de, OaksPCText + call PlaceString + ld a, [wNumHoFTeams] + and a + jr z, .noLeaguePC + ld a, 4 + ld [wMaxMenuItem], a + coord hl, 2, 8 + ld de, PKMNLeaguePCText + call PlaceString + coord hl, 2, 10 + ld de, LogOffPCText + jr .next3 +.noLeaguePC + coord hl, 2, 8 + ld de, LogOffPCText + jr .next3 +.noOaksPC2 + ld a, $2 + ld [wMaxMenuItem], a + coord hl, 2, 6 + ld de, LogOffPCText +.next3 + call PlaceString + ld a, A_BUTTON | B_BUTTON + ld [wMenuWatchedKeys], a + ld a, 2 + ld [wTopMenuItemY], a + ld a, 1 + ld [wTopMenuItemX], a + xor a + ld [wCurrentMenuItem], a + ld [wLastMenuItem], a + ld a, 1 + ld [H_AUTOBGTRANSFERENABLED], a + ret + +SomeonesPCText: db "JEMANDES PC@" +BillsPCText: db "BILLS PC@" +PlayersPCText: db "PC VON @" +OaksPCText: db "EICHS PC@" +PKMNLeaguePCText: db "<pkmn>-LIGA@" +LogOffPCText: db "AUSLOGGEN@" + +BillsPC_:: + ld hl, wd730 + set 6, [hl] + xor a + ld [wParentMenuItem], a + inc a ; MONSTER_NAME + ld [wNameListType], a + call LoadHpBarAndStatusTilePatterns + ld a, [wListScrollOffset] + push af + ld a, [wFlags_0xcd60] + bit 3, a ; accessing Bill's PC through another PC? + jr nz, BillsPCMenu +; accessing it directly + ld a, $99 + call PlaySound + ld hl, SwitchOnText + call PrintText + +BillsPCMenu: + ld a, [wParentMenuItem] + ld [wCurrentMenuItem], a + ld hl, vChars2 + $780 + ld de, PokeballTileGraphics + lb bc, BANK(PokeballTileGraphics), $01 + call CopyVideoData + call LoadScreenTilesFromBuffer2DisableBGTransfer + coord hl, 0, 0 + ld b, 10 + ld c, 14 + call TextBoxBorder + coord hl, 2, 2 + ld de, BillsPCMenuText + call PlaceString + ld hl, wTopMenuItemY + ld a, 2 + ld [hli], a ; wTopMenuItemY + dec a + ld [hli], a ; wTopMenuItemX + inc hl + inc hl + ld a, 4 + ld [hli], a ; wMaxMenuItem + ld a, A_BUTTON | B_BUTTON + ld [hli], a ; wMenuWatchedKeys + xor a + ld [hli], a ; wLastMenuItem + ld [hli], a ; wPartyAndBillsPCSavedMenuItem + ld hl, wListScrollOffset + ld [hli], a ; wListScrollOffset + ld [hl], a ; wMenuWatchMovingOutOfBounds + ld [wPlayerMonNumber], a + ld hl, WhatText + call PrintText + coord hl, 9, 14 + ld b, 2 + ld c, 9 + call TextBoxBorder + ld a, [wCurrentBoxNum] + and $7f + cp 9 + jr c, .singleDigitBoxNum +; two digit box num + sub 9 + coord hl, 17, 16 + ld [hl], "1" + add "0" + jr .next +.singleDigitBoxNum + add "1" +.next + Coorda 18, 16 + coord hl, 10, 16 + ld de, BoxNoPCText + call PlaceString + ld a, 1 + ld [H_AUTOBGTRANSFERENABLED], a + call Delay3 + call HandleMenuInput + bit 1, a + jp nz, ExitBillsPC ; b button + call PlaceUnfilledArrowMenuCursor + ld a, [wCurrentMenuItem] + ld [wParentMenuItem], a + and a + jp z, BillsPCWithdraw ; withdraw + cp $1 + jp z, BillsPCDeposit ; deposit + cp $2 + jp z, BillsPCRelease ; release + cp $3 + jp z, BillsPCChangeBox ; change box + +ExitBillsPC: + ld a, [wFlags_0xcd60] + bit 3, a ; accessing Bill's PC through another PC? + jr nz, .next +; accessing it directly + call LoadTextBoxTilePatterns + ld a, $9a + call PlaySound + call WaitForSoundToFinish +.next + ld hl, wFlags_0xcd60 + res 5, [hl] + call LoadScreenTilesFromBuffer2 + pop af + ld [wListScrollOffset], a + ld hl, wd730 + res 6, [hl] + ret + +BillsPCDeposit: + ld a, [wPartyCount] + dec a + jr nz, .partyLargeEnough + ld hl, CantDepositLastMonText + call PrintText + jp BillsPCMenu +.partyLargeEnough + ld a, [wNumInBox] + cp MONS_PER_BOX + jr nz, .boxNotFull + ld hl, BoxFullText + call PrintText + jp BillsPCMenu +.boxNotFull + ld hl, wPartyCount + call DisplayMonListMenu + jp c, BillsPCMenu + call DisplayDepositWithdrawMenu + jp nc, BillsPCMenu + ld a, [wcf91] + call GetCryData + call PlaySoundWaitForCurrent + ld a, PARTY_TO_BOX + ld [wMoveMonType], a + call MoveMon + xor a + ld [wRemoveMonFromBox], a + call RemovePokemon + call WaitForSoundToFinish + ld hl, wBoxNumString + ld a, [wCurrentBoxNum] + and $7f + cp 9 + jr c, .singleDigitBoxNum + sub 9 + ld [hl], "1" + inc hl + add "0" + jr .next +.singleDigitBoxNum + add "1" +.next + ld [hli], a + ld [hl], "@" + ld hl, MonWasStoredText + call PrintText + jp BillsPCMenu + +BillsPCWithdraw: + ld a, [wNumInBox] + and a + jr nz, .boxNotEmpty + ld hl, NoMonText + call PrintText + jp BillsPCMenu +.boxNotEmpty + ld a, [wPartyCount] + cp PARTY_LENGTH + jr nz, .partyNotFull + ld hl, CantTakeMonText + call PrintText + jp BillsPCMenu +.partyNotFull + ld hl, wNumInBox + call DisplayMonListMenu + jp c, BillsPCMenu + call DisplayDepositWithdrawMenu + jp nc, BillsPCMenu + ld a, [wWhichPokemon] + ld hl, wBoxMonNicks + call GetPartyMonName + ld a, [wcf91] + call GetCryData + call PlaySoundWaitForCurrent + xor a ; BOX_TO_PARTY + ld [wMoveMonType], a + call MoveMon + ld a, 1 + ld [wRemoveMonFromBox], a + call RemovePokemon + call WaitForSoundToFinish + ld hl, MonIsTakenOutText + call PrintText + jp BillsPCMenu + +BillsPCRelease: + ld a, [wNumInBox] + and a + jr nz, .loop + ld hl, NoMonText + call PrintText + jp BillsPCMenu +.loop + ld hl, wNumInBox + call DisplayMonListMenu + jp c, BillsPCMenu + ld hl, OnceReleasedText + call PrintText + call YesNoChoice + ld a, [wCurrentMenuItem] + and a + jr nz, .loop + inc a + ld [wRemoveMonFromBox], a + call RemovePokemon + call WaitForSoundToFinish + ld a, [wcf91] + call PlayCry + ld hl, MonWasReleasedText + call PrintText + jp BillsPCMenu + +BillsPCChangeBox: + callba ChangeBox + jp BillsPCMenu + +DisplayMonListMenu: + ld a, l + ld [wListPointer], a + ld a, h + ld [wListPointer + 1], a + xor a + ld [wPrintItemPrices], a + ld [wListMenuID], a + inc a ; MONSTER_NAME + ld [wNameListType], a + ld a, [wPartyAndBillsPCSavedMenuItem] + ld [wCurrentMenuItem], a + call DisplayListMenuID + ld a, [wCurrentMenuItem] + ld [wPartyAndBillsPCSavedMenuItem], a + ret + +BillsPCMenuText: + db "<pkmn> MITNEHMEN" + next "<pkmn> ABLEGEN" + next "<pkmn> FREILASSEN" + next "BOX WECHSELN" + next "TSCHÜSS!" + db "@" + +BoxNoPCText: + db "BOX Nr.@" + +KnowsHMMove:: +; returns whether mon with party index [wWhichPokemon] knows an HM move + ld hl, wPartyMon1Moves + ld bc, wPartyMon2 - wPartyMon1 + jr .next +; unreachable + ld hl, wBoxMon1Moves + ld bc, wBoxMon2 - wBoxMon1 +.next + ld a, [wWhichPokemon] + call AddNTimes + ld b, NUM_MOVES +.loop + ld a, [hli] + push hl + push bc + ld hl, HMMoveArray + ld de, 1 + call IsInArray + pop bc + pop hl + ret c + dec b + jr nz, .loop + and a + ret + +HMMoveArray: + db CUT + db FLY + db SURF + db STRENGTH + db FLASH + db -1 + +DisplayDepositWithdrawMenu: + coord hl, 8, 10 + ld b, 6 + ld c, 10 + call TextBoxBorder + ld a, [wParentMenuItem] + and a ; was the Deposit or Withdraw item selected in the parent menu? + ld de, DepositPCText + jr nz, .next + ld de, WithdrawPCText +.next + coord hl, 10, 12 + call PlaceString + coord hl, 10, 14 + ld de, StatsCancelPCText + call PlaceString + ld hl, wTopMenuItemY + ld a, 12 + ld [hli], a ; wTopMenuItemY + ld a, 9 + ld [hli], a ; wTopMenuItemX + xor a + ld [hli], a ; wCurrentMenuItem + inc hl + ld a, 2 + ld [hli], a ; wMaxMenuItem + ld a, A_BUTTON | B_BUTTON + ld [hli], a ; wMenuWatchedKeys + xor a + ld [hl], a ; wLastMenuItem + ld hl, wListScrollOffset + ld [hli], a ; wListScrollOffset + ld [hl], a ; wMenuWatchMovingOutOfBounds + ld [wPlayerMonNumber], a + ld [wPartyAndBillsPCSavedMenuItem], a +.loop + call HandleMenuInput + bit 1, a ; pressed B? + jr nz, .exit + ld a, [wCurrentMenuItem] + and a + jr z, .choseDepositWithdraw + dec a + jr z, .viewStats +.exit + and a + ret +.choseDepositWithdraw + scf + ret +.viewStats + call SaveScreenTilesToBuffer1 + ld a, [wParentMenuItem] + and a + ld a, PLAYER_PARTY_DATA + jr nz, .next2 + ld a, BOX_DATA +.next2 + ld [wMonDataLocation], a + predef StatusScreen + predef StatusScreen2 + call LoadScreenTilesFromBuffer1 + call ReloadTilesetTilePatterns + call RunDefaultPaletteCommand + call LoadGBPal + jr .loop + +DepositPCText: db "ABLEGEN@" +WithdrawPCText: db "MITNEHMEN@" +StatsCancelPCText: + db "STATUS" + next "ZURÜCK@" + +SwitchOnText: + TX_FAR _SwitchOnText + db "@" + +WhatText: + TX_FAR _WhatText + db "@" + +DepositWhichMonText: + TX_FAR _DepositWhichMonText + db "@" + +MonWasStoredText: + TX_FAR _MonWasStoredText + db "@" + +CantDepositLastMonText: + TX_FAR _CantDepositLastMonText + db "@" + +BoxFullText: + TX_FAR _BoxFullText + db "@" + +MonIsTakenOutText: + TX_FAR _MonIsTakenOutText + db "@" + +NoMonText: + TX_FAR _NoMonText + db "@" + +CantTakeMonText: + TX_FAR _CantTakeMonText + db "@" + +ReleaseWhichMonText: + TX_FAR _ReleaseWhichMonText + db "@" + +OnceReleasedText: + TX_FAR _OnceReleasedText + db "@" + +MonWasReleasedText: + TX_FAR _MonWasReleasedText + db "@" + +CableClubLeftGameboy:: + ld a, [hSerialConnectionStatus] + cp USING_EXTERNAL_CLOCK + ret z + ld a, [wSpriteStateData1 + 9] ; player's sprite facing direction + cp SPRITE_FACING_RIGHT + ret nz + ld a, [wCurMap] + cp TRADE_CENTER + ld a, LINK_STATE_START_TRADE + jr z, .next + inc a ; LINK_STATE_START_BATTLE +.next + ld [wLinkState], a + call EnableAutoTextBoxDrawing + tx_pre_jump JustAMomentText + +CableClubRightGameboy:: + ld a, [hSerialConnectionStatus] + cp USING_INTERNAL_CLOCK + ret z + ld a, [wSpriteStateData1 + 9] ; player's sprite facing direction + cp SPRITE_FACING_LEFT + ret nz + ld a, [wCurMap] + cp TRADE_CENTER + ld a, LINK_STATE_START_TRADE + jr z, .next + inc a ; LINK_STATE_START_BATTLE +.next + ld [wLinkState], a + call EnableAutoTextBoxDrawing + tx_pre_jump JustAMomentText + +JustAMomentText:: + TX_FAR _JustAMomentText + db "@" + + ld a, [wSpriteStateData1 + 9] ; player's sprite facing direction + cp SPRITE_FACING_UP + ret nz + call EnableAutoTextBoxDrawing + tx_pre_jump OpenBillsPCText + +OpenBillsPCText:: + db $FD ; FuncTX_BillsPC + diff --git a/de/engine/menu/diploma.asm b/de/engine/menu/diploma.asm new file mode 100755 index 00000000..9f559b8a --- /dev/null +++ b/de/engine/menu/diploma.asm @@ -0,0 +1,112 @@ +DisplayDiploma: + call SaveScreenTilesToBuffer2 + call GBPalWhiteOutWithDelay3 + call ClearScreen + xor a + ld [wUpdateSpritesEnabled], a + ld hl, wd730 + set 6, [hl] + call DisableLCD + ld hl, CircleTile + ld de, vChars2 + $700 + ld bc, $0010 + ld a, BANK(CircleTile) + call FarCopyData2 + coord hl, 0, 0 + lb bc, 16, 18 + predef Diploma_TextBoxBorder + ld hl, DiplomaTextPointersAndCoords + ld c, $5 +.asm_56715 + push bc + ld a, [hli] + ld e, a + ld a, [hli] + ld d, a + ld a, [hli] + push hl + ld h, [hl] + ld l, a + call PlaceString + pop hl + inc hl + pop bc + dec c + jr nz, .asm_56715 + coord hl, 9, 6 + ld de, wPlayerName + call PlaceString + callba DrawPlayerCharacter + +; Move the player 33 pixels right and set the priority bit so he appears +; behind the background layer. + ld hl, wOAMBuffer + $01 + lb bc, $80, $28 +.adjustPlayerGfxLoop + ld a, [hl] ; X + add 33 + ld [hli], a + inc hl + ld a, b + ld [hli], a ; attributes + inc hl + dec c + jr nz, .adjustPlayerGfxLoop + + call EnableLCD + callba LoadTrainerInfoTextBoxTiles + ld b, SET_PAL_GENERIC + call RunPaletteCommand + call Delay3 + call GBPalNormal + ld a, $90 + ld [rOBP0], a + call WaitForTextScrollButtonPress + ld hl, wd730 + res 6, [hl] + call GBPalWhiteOutWithDelay3 + call RestoreScreenTilesAndReloadTilePatterns + call Delay3 + jp GBPalNormal + +UnusedPlayerNameLengthFunc: +; Unused function that does a calculation involving the length of the player's +; name. + ld hl, wPlayerName + ld bc, $ff00 +.loop + ld a, [hli] + cp "@" + ret z + dec c + jr .loop + +DiplomaTextPointersAndCoords: + dw DiplomaText + dwCoord 6, 2 + dw DiplomaPlayer1 + dwCoord 2, 4 + dw DiplomaPlayer2 + dwCoord 2, 6 + dw DiplomaCongrats + dwCoord 2, 8 + dw DiplomaGameFreak + dwCoord 9, 16 + +DiplomaText: + db $70,"Diplom",$70,"@" + +DiplomaPlayer1: + db "Herzlichen Glück-@" + +DiplomaPlayer2: + db "wunsch !@" + +DiplomaCongrats: + db "Du hast es ge-" + next "schafft, den" + next "#DEX zu" + next "vervollständigen@" + +DiplomaGameFreak: + db "GAME FREAK@" diff --git a/de/engine/menu/draw_start_menu.asm b/de/engine/menu/draw_start_menu.asm new file mode 100644 index 00000000..5e10b972 --- /dev/null +++ b/de/engine/menu/draw_start_menu.asm @@ -0,0 +1,89 @@ +; function that displays the start menu +DrawStartMenu: + CheckEvent EVENT_GOT_POKEDEX +; menu with pokedex + coord hl, 10, 0 + ld b,$0e + ld c,$08 + jr nz,.drawTextBoxBorder +; shorter menu if the player doesn't have the pokedex + coord hl, 10, 0 + ld b,$0c + ld c,$08 +.drawTextBoxBorder + call TextBoxBorder + ld a,D_DOWN | D_UP | START | B_BUTTON | A_BUTTON + ld [wMenuWatchedKeys],a + ld a,$02 + ld [wTopMenuItemY],a ; Y position of first menu choice + ld a,$0b + ld [wTopMenuItemX],a ; X position of first menu choice + ld a,[wBattleAndStartSavedMenuItem] ; remembered menu selection from last time + ld [wCurrentMenuItem],a + ld [wLastMenuItem],a + xor a + ld [wMenuWatchMovingOutOfBounds],a + ld hl,wd730 + set 6,[hl] ; no pauses between printing each letter + coord hl, 12, 2 + CheckEvent EVENT_GOT_POKEDEX +; case for not having pokedex + ld a,$06 + jr z,.storeMenuItemCount +; case for having pokedex + ld de,StartMenuPokedexText + call PrintStartMenuItem + ld a,$07 +.storeMenuItemCount + ld [wMaxMenuItem],a ; number of menu items + ld de,StartMenuPokemonText + call PrintStartMenuItem + ld de,StartMenuItemText + call PrintStartMenuItem + ld de,wPlayerName ; player's name + call PrintStartMenuItem + ld a,[wd72e] + bit 6,a ; is the player using the link feature? +; case for not using link feature + ld de,StartMenuSaveText + jr z,.printSaveOrResetText +; case for using link feature + ld de,StartMenuResetText +.printSaveOrResetText + call PrintStartMenuItem + ld de,StartMenuOptionText + call PrintStartMenuItem + ld de,StartMenuExitText + call PlaceString + ld hl,wd730 + res 6,[hl] ; turn pauses between printing letters back on + ret + +StartMenuPokedexText: + db "#DEX@" + +StartMenuPokemonText: + db "#MON@" + +StartMenuItemText: + db "ITEM@" + +StartMenuSaveText: + db "SICHERN@" + +StartMenuResetText: + db "RESET@" + +StartMenuExitText: + db "ZURÜCK@" + +StartMenuOptionText: + db "OPTION@" + +PrintStartMenuItem: + push hl + call PlaceString + pop hl + ld de,SCREEN_WIDTH * 2 + add hl,de + ret diff --git a/de/engine/menu/league_pc.asm b/de/engine/menu/league_pc.asm new file mode 100755 index 00000000..8ca8e1e3 --- /dev/null +++ b/de/engine/menu/league_pc.asm @@ -0,0 +1,120 @@ +PKMNLeaguePC: + ld hl, AccessedHoFPCText + call PrintText + ld hl, wd730 + set 6, [hl] + push hl + ld a, [wUpdateSpritesEnabled] + push af + ld a, [hTilesetType] + push af + xor a + ld [hTilesetType], a + ld [wSpriteFlipped], a + ld [wUpdateSpritesEnabled], a + ld [wHoFTeamIndex2], a + ld [wHoFTeamNo], a + ld a, [wNumHoFTeams] + ld b, a + cp HOF_TEAM_CAPACITY + 1 + jr c, .loop +; If the total number of hall of fame teams is greater than the storage +; capacity, then calculate the number of the first team that is still recorded. + ld b, HOF_TEAM_CAPACITY + sub b + ld [wHoFTeamNo], a +.loop + ld hl, wHoFTeamNo + inc [hl] + push bc + ld a, [wHoFTeamIndex2] + ld [wHoFTeamIndex], a + callba LoadHallOfFameTeams + call LeaguePCShowTeam + pop bc + jr c, .doneShowingTeams + ld hl, wHoFTeamIndex2 + inc [hl] + ld a, [hl] + cp b + jr nz, .loop +.doneShowingTeams + pop af + ld [hTilesetType], a + pop af + ld [wUpdateSpritesEnabled], a + pop hl + res 6, [hl] + call GBPalWhiteOutWithDelay3 + call ClearScreen + call RunDefaultPaletteCommand + jp GBPalNormal + +LeaguePCShowTeam: + ld c, PARTY_LENGTH +.loop + push bc + call LeaguePCShowMon + call WaitForTextScrollButtonPress + ld a, [hJoyHeld] + bit 1, a + jr nz, .exit + ld hl, wHallOfFame + HOF_MON + ld de, wHallOfFame + ld bc, HOF_TEAM - HOF_MON + call CopyData + pop bc + ld a, [wHallOfFame + 0] + cp $ff + jr z, .done + dec c + jr nz, .loop +.done + and a + ret +.exit + pop bc + scf + ret + +LeaguePCShowMon: + call GBPalWhiteOutWithDelay3 + call ClearScreen + ld hl, wHallOfFame + ld a, [hli] + ld [wHoFMonSpecies], a + ld [wcf91], a + ld [wd0b5], a + ld [wBattleMonSpecies2], a + ld [wWholeScreenPaletteMonSpecies], a + ld a, [hli] + ld [wHoFMonLevel], a + ld de, wcd6d + ld bc, NAME_LENGTH + call CopyData + ld b, SET_PAL_POKEMON_WHOLE_SCREEN + ld c, 0 + call RunPaletteCommand + coord hl, 12, 5 + call GetMonHeader + call LoadFrontSpriteByMonIndex + call GBPalNormal + coord hl, 0, 13 + ld b, 2 + ld c, $12 + call TextBoxBorder + coord hl, 1, 15 + ld de, HallOfFameNoText + call PlaceString + coord hl, 16, 15 + ld de, wHoFTeamNo + lb bc, 1, 3 + call PrintNumber + jpba HoFDisplayMonInfo + +HallOfFameNoText: + db "RUHMESHALLE Nr.@" + +AccessedHoFPCText: + TX_FAR _AccessedHoFPCText + db "@" diff --git a/de/engine/menu/main_menu.asm b/de/engine/menu/main_menu.asm new file mode 100755 index 00000000..2da68a3b --- /dev/null +++ b/de/engine/menu/main_menu.asm @@ -0,0 +1,712 @@ +MainMenu: +; Check save file + call InitOptions + xor a + ld [wOptionsInitialized],a + inc a + ld [wSaveFileStatus],a + call CheckForPlayerNameInSRAM + jr nc,.mainMenuLoop + + predef LoadSAV + +.mainMenuLoop + ld c,20 + call DelayFrames + xor a ; LINK_STATE_NONE + ld [wLinkState],a + ld hl,wPartyAndBillsPCSavedMenuItem + ld [hli],a + ld [hli],a + ld [hli],a + ld [hl],a + ld [wDefaultMap],a + ld hl,wd72e + res 6,[hl] + call ClearScreen + call RunDefaultPaletteCommand + call LoadTextBoxTilePatterns + call LoadFontTilePatterns + ld hl,wd730 + set 6,[hl] + ld a,[wSaveFileStatus] + cp a,1 + jr z,.noSaveFile +; there's a save file + coord hl, 0, 0 + ld b,6 + ld c,13 + call TextBoxBorder + coord hl, 2, 2 + ld de,ContinueText + call PlaceString + jr .next2 +.noSaveFile + coord hl, 0, 0 + ld b,4 + ld c,13 + call TextBoxBorder + coord hl, 2, 2 + ld de,NewGameText + call PlaceString +.next2 + ld hl,wd730 + res 6,[hl] + call UpdateSprites + xor a + ld [wCurrentMenuItem],a + ld [wLastMenuItem],a + ld [wMenuJoypadPollCount],a + inc a + ld [wTopMenuItemX],a + inc a + ld [wTopMenuItemY],a + ld a,A_BUTTON | B_BUTTON | START + ld [wMenuWatchedKeys],a + ld a,[wSaveFileStatus] + ld [wMaxMenuItem],a + call HandleMenuInput + bit 1,a ; pressed B? + jp nz,DisplayTitleScreen ; if so, go back to the title screen + ld c,20 + call DelayFrames + ld a,[wCurrentMenuItem] + ld b,a + ld a,[wSaveFileStatus] + cp a,2 + jp z,.skipInc +; If there's no save file, increment the current menu item so that the numbers +; are the same whether or not there's a save file. + inc b +.skipInc + ld a,b + and a + jr z,.choseContinue + cp a,1 + jp z,StartNewGame + call DisplayOptionMenu + ld a,1 + ld [wOptionsInitialized],a + jp .mainMenuLoop +.choseContinue + call DisplayContinueGameInfo + ld hl,wCurrentMapScriptFlags + set 5,[hl] +.inputLoop + xor a + ld [hJoyPressed],a + ld [hJoyReleased],a + ld [hJoyHeld],a + call Joypad + ld a,[hJoyHeld] + bit 0,a + jr nz,.pressedA + bit 1,a + jp nz,.mainMenuLoop ; pressed B + jr .inputLoop +.pressedA + call GBPalWhiteOutWithDelay3 + call ClearScreen + ld a,PLAYER_DIR_DOWN + ld [wPlayerDirection],a + ld c,10 + call DelayFrames + ld a,[wNumHoFTeams] + and a + jp z,SpecialEnterMap + ld a,[wCurMap] ; map ID + cp a,HALL_OF_FAME + jp nz,SpecialEnterMap + xor a + ld [wDestinationMap],a + ld hl,wd732 + set 2,[hl] ; fly warp or dungeon warp + call SpecialWarpIn + jp SpecialEnterMap + +InitOptions: + ld a,1 ; no delay + ld [wLetterPrintingDelayFlags],a + ld a,3 ; medium speed + ld [wOptions],a + ret + +LinkMenu: + xor a + ld [wLetterPrintingDelayFlags], a + ld hl, wd72e + set 6, [hl] + ld hl, TextTerminator_6b20 + call PrintText + call SaveScreenTilesToBuffer1 + ld hl, WhereWouldYouLikeText + call PrintText + coord hl, 4, 5 + ld b, $6 + ld c, $e + call TextBoxBorder + call UpdateSprites + coord hl, 6, 7 + ld de, CableClubOptionsText + call PlaceString + xor a + ld [wUnusedCD37], a + ld [wd72d], a + ld hl, wTopMenuItemY + ld a, $7 + ld [hli], a + ld a, $5 + ld [hli], a + xor a + ld [hli], a + inc hl + ld a, $2 + ld [hli], a + inc a + ; ld a, A_BUTTON | B_BUTTON + ld [hli], a ; wMenuWatchedKeys + xor a + ld [hl], a +.waitForInputLoop + call HandleMenuInput + and A_BUTTON | B_BUTTON + add a + add a + ld b, a + ld a, [wCurrentMenuItem] + add b + add $d0 + ld [wLinkMenuSelectionSendBuffer], a + ld [wLinkMenuSelectionSendBuffer + 1], a +.exchangeMenuSelectionLoop + call Serial_ExchangeLinkMenuSelection + ld a, [wLinkMenuSelectionReceiveBuffer] + ld b, a + and $f0 + cp $d0 + jr z, .asm_5c7d + ld a, [wLinkMenuSelectionReceiveBuffer + 1] + ld b, a + and $f0 + cp $d0 + jr nz, .exchangeMenuSelectionLoop +.asm_5c7d + ld a, b + and $c ; did the enemy press A or B? + jr nz, .enemyPressedAOrB +; the enemy didn't press A or B + ld a, [wLinkMenuSelectionSendBuffer] + and $c ; did the player press A or B? + jr z, .waitForInputLoop ; if neither the player nor the enemy pressed A or B, try again + jr .doneChoosingMenuSelection ; if the player pressed A or B but the enemy didn't, use the player's selection +.enemyPressedAOrB + ld a, [wLinkMenuSelectionSendBuffer] + and $c ; did the player press A or B? + jr z, .useEnemyMenuSelection ; if the enemy pressed A or B but the player didn't, use the enemy's selection +; the enemy and the player both pressed A or B +; The gameboy that is clocking the connection wins. + ld a, [hSerialConnectionStatus] + cp USING_INTERNAL_CLOCK + jr z, .doneChoosingMenuSelection +.useEnemyMenuSelection + ld a, b + ld [wLinkMenuSelectionSendBuffer], a + and $3 + ld [wCurrentMenuItem], a +.doneChoosingMenuSelection + ld a, [hSerialConnectionStatus] + cp USING_INTERNAL_CLOCK + jr nz, .skipStartingTransfer + call DelayFrame + call DelayFrame + ld a, START_TRANSFER_INTERNAL_CLOCK + ld [rSC], a +.skipStartingTransfer + ld b, $7f + ld c, $7f + ld d, $ec + ld a, [wLinkMenuSelectionSendBuffer] + and (B_BUTTON << 2) ; was B button pressed? + jr nz, .updateCursorPosition +; A button was pressed + ld a, [wCurrentMenuItem] + cp $2 + jr z, .updateCursorPosition + ld c, d + ld d, b + dec a + jr z, .updateCursorPosition + ld b, c + ld c, d +.updateCursorPosition + ld a, b + Coorda 5, 7 + ld a, c + Coorda 5, 9 + ld a, d + Coorda 5, 11 + ld c, 40 + call DelayFrames + call LoadScreenTilesFromBuffer1 + ld a, [wLinkMenuSelectionSendBuffer] + and (B_BUTTON << 2) ; was B button pressed? + jr nz, .choseCancel ; cancel if B pressed + ld a, [wCurrentMenuItem] + cp $2 + jr z, .choseCancel + xor a + ld [wWalkBikeSurfState], a ; start walking + ld a, [wCurrentMenuItem] + and a + ld a, COLOSSEUM + jr nz, .next + ld a, TRADE_CENTER +.next + ld [wd72d], a + ld hl, PleaseWaitText + call PrintText + ld c, 50 + call DelayFrames + ld hl, wd732 + res 1, [hl] + ld a, [wDefaultMap] + ld [wDestinationMap], a + call SpecialWarpIn + ld c, 20 + call DelayFrames + xor a + ld [wMenuJoypadPollCount], a + ld [wSerialExchangeNybbleSendData], a + inc a ; LINK_STATE_IN_CABLE_CLUB + ld [wLinkState], a + ld [wEnteringCableClub], a + jr SpecialEnterMap +.choseCancel + xor a + ld [wMenuJoypadPollCount], a + call Delay3 + call CloseLinkConnection + ld hl, LinkCanceledText + call PrintText + ld hl, wd72e + res 6, [hl] + ret + +WhereWouldYouLikeText: + TX_FAR _WhereWouldYouLikeText + db "@" + +PleaseWaitText: + TX_FAR _PleaseWaitText + db "@" + +LinkCanceledText: + TX_FAR _LinkCanceledText + db "@" + +StartNewGame: + ld hl, wd732 + res 1, [hl] + call OakSpeech + ld c, 20 + call DelayFrames + +; enter map after using a special warp or loading the game from the main menu +SpecialEnterMap: + xor a + ld [hJoyPressed], a + ld [hJoyHeld], a + ld [hJoy5], a + ld [wd72d], a + ld hl, wd732 + set 0, [hl] ; count play time + call ResetPlayerSpriteData + ld c, 20 + call DelayFrames + ld a, [wEnteringCableClub] + and a + ret nz + jp EnterMap + +ContinueText: + db "WEITER", $4e + +NewGameText: + db "NEUES SPIEL" + next "OPTIONEN@" + +CableClubOptionsText: + db "HANDELSCENTER" + next "KOLOSSEUM" + next "ZURÜCK@" + +DisplayContinueGameInfo: + xor a + ld [H_AUTOBGTRANSFERENABLED], a + coord hl, 3, 7 + ld b, 8 + ld c, 15 + call TextBoxBorder + coord hl, 4, 9 + ld de, SaveScreenInfoText + call PlaceString + coord hl, 12, 9 + ld de, wPlayerName + call PlaceString + coord hl, 17, 11 + call PrintNumBadges + coord hl, 16, 13 + call PrintNumOwnedMons + coord hl, 13, 15 + call PrintPlayTime + ld a, 1 + ld [H_AUTOBGTRANSFERENABLED], a + ld c, 30 + jp DelayFrames + +PrintSaveScreenText: + xor a + ld [H_AUTOBGTRANSFERENABLED], a + coord hl, 3, 0 + ld b, $8 + ld c, $f + call TextBoxBorder + call LoadTextBoxTilePatterns + call UpdateSprites + coord hl, 4, 2 + ld de, SaveScreenInfoText + call PlaceString + coord hl, 12, 2 + ld de, wPlayerName + call PlaceString + coord hl, 17, 4 + call PrintNumBadges + coord hl, 16, 6 + call PrintNumOwnedMons + coord hl, 13, 8 + call PrintPlayTime + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + ld c, 30 + jp DelayFrames + +PrintNumBadges: + push hl + ld hl, wObtainedBadges + ld b, $1 + call CountSetBits + pop hl + ld de, wNumSetBits + lb bc, 1, 2 + jp PrintNumber + +PrintNumOwnedMons: + push hl + ld hl, wPokedexOwned + ld b, wPokedexOwnedEnd - wPokedexOwned + call CountSetBits + pop hl + ld de, wNumSetBits + lb bc, 1, 3 + jp PrintNumber + +PrintPlayTime: + ld de, wPlayTimeHours + lb bc, 1, 3 + call PrintNumber + ld [hl], $6d + inc hl + ld de, wPlayTimeMinutes + lb bc, LEADING_ZEROES | 1, 2 + jp PrintNumber + +SaveScreenInfoText: + db "SPIELER" + next "ORDEN " + next "#DEX " + next "ZEIT@" + +DisplayOptionMenu: + coord hl, 0, 0 + ld b,3 + ld c,18 + call TextBoxBorder + coord hl, 0, 5 + ld b,3 + ld c,18 + call TextBoxBorder + coord hl, 0, 10 + ld b,3 + ld c,18 + call TextBoxBorder + coord hl, 1, 1 + ld de,TextSpeedOptionText + call PlaceString + coord hl, 1, 6 + ld de,BattleAnimationOptionText + call PlaceString + coord hl, 1, 11 + ld de,BattleStyleOptionText + call PlaceString + coord hl, 2, 16 + ld de,OptionMenuCancelText + call PlaceString + xor a + ld [wCurrentMenuItem],a + ld [wLastMenuItem],a + inc a + ld [wLetterPrintingDelayFlags],a + ld [wUnusedCD40],a + ld a,3 ; text speed cursor Y coordinate + ld [wTopMenuItemY],a + call SetCursorPositionsFromOptions + ld a,[wOptionsTextSpeedCursorX] ; text speed cursor X coordinate + ld [wTopMenuItemX],a + ld a,$01 + ld [H_AUTOBGTRANSFERENABLED],a ; enable auto background transfer + call Delay3 +.loop + call PlaceMenuCursor + call SetOptionsFromCursorPositions +.getJoypadStateLoop + call JoypadLowSensitivity + ld a,[hJoy5] + ld b,a + and a,A_BUTTON | B_BUTTON | START | D_RIGHT | D_LEFT | D_UP | D_DOWN ; any key besides select pressed? + jr z,.getJoypadStateLoop + bit 1,b ; B button pressed? + jr nz,.exitMenu + bit 3,b ; Start button pressed? + jr nz,.exitMenu + bit 0,b ; A button pressed? + jr z,.checkDirectionKeys + ld a,[wTopMenuItemY] + cp a,16 ; is the cursor on Cancel? + jr nz,.loop +.exitMenu + ld a,SFX_PRESS_AB + call PlaySound + ret +.eraseOldMenuCursor + ld [wTopMenuItemX],a + call EraseMenuCursor + jp .loop +.checkDirectionKeys + ld a,[wTopMenuItemY] + bit 7,b ; Down pressed? + jr nz,.downPressed + bit 6,b ; Up pressed? + jr nz,.upPressed + cp a,8 ; cursor in Battle Animation section? + jr z,.cursorInBattleAnimation + cp a,13 ; cursor in Battle Style section? + jr z,.cursorInBattleStyle + cp a,16 ; cursor on Cancel? + jr z,.loop +.cursorInTextSpeed + bit 5,b ; Left pressed? + jp nz,.pressedLeftInTextSpeed + jp .pressedRightInTextSpeed +.downPressed + cp a,16 + ld b,-13 + ld hl,wOptionsTextSpeedCursorX + jr z,.updateMenuVariables + ld b,5 + cp a,3 + inc hl + jr z,.updateMenuVariables + cp a,8 + inc hl + jr z,.updateMenuVariables + ld b,3 + inc hl + jr .updateMenuVariables +.upPressed + cp a,8 + ld b,-5 + ld hl,wOptionsTextSpeedCursorX + jr z,.updateMenuVariables + cp a,13 + inc hl + jr z,.updateMenuVariables + cp a,16 + ld b,-3 + inc hl + jr z,.updateMenuVariables + ld b,13 + inc hl +.updateMenuVariables + add b + ld [wTopMenuItemY],a + ld a,[hl] + ld [wTopMenuItemX],a + call PlaceUnfilledArrowMenuCursor + jp .loop +.cursorInBattleAnimation + ld a,[wOptionsBattleAnimCursorX] ; battle animation cursor X coordinate + xor a,$0b ; toggle between 1 and 10 + ld [wOptionsBattleAnimCursorX],a + jp .eraseOldMenuCursor +.cursorInBattleStyle + ld a,[wOptionsBattleStyleCursorX] ; battle style cursor X coordinate + xor a,$0b ; toggle between 1 and 10 + ld [wOptionsBattleStyleCursorX],a + jp .eraseOldMenuCursor +.pressedLeftInTextSpeed + ld a,[wOptionsTextSpeedCursorX] ; text speed cursor X coordinate + cp a,1 + jr z,.updateTextSpeedXCoord + cp a,7 + jr nz,.fromSlowToMedium + sub a,6 + jr .updateTextSpeedXCoord +.fromSlowToMedium + sub a,7 + jr .updateTextSpeedXCoord +.pressedRightInTextSpeed + ld a,[wOptionsTextSpeedCursorX] ; text speed cursor X coordinate + cp a,14 + jr z,.updateTextSpeedXCoord + cp a,7 + jr nz,.fromFastToMedium + add a,7 + jr .updateTextSpeedXCoord +.fromFastToMedium + add a,6 +.updateTextSpeedXCoord + ld [wOptionsTextSpeedCursorX],a ; text speed cursor X coordinate + jp .eraseOldMenuCursor + +TextSpeedOptionText: + db "TEXT-TEMPO" + next " 3 2 1 @" + +BattleAnimationOptionText: + db "KAMPFANIMATION" + next " AN AUS@" + +BattleStyleOptionText: + db "KAMPFSTIL" + next " WECHSEL FOLGEND@" + +OptionMenuCancelText: + db "ZURÜCK@" + +; sets the options variable according to the current placement of the menu cursors in the options menu +SetOptionsFromCursorPositions: + ld hl,TextSpeedOptionData + ld a,[wOptionsTextSpeedCursorX] ; text speed cursor X coordinate + ld c,a +.loop + ld a,[hli] + cp c + jr z,.textSpeedMatchFound + inc hl + jr .loop +.textSpeedMatchFound + ld a,[hl] + ld d,a + ld a,[wOptionsBattleAnimCursorX] ; battle animation cursor X coordinate + dec a + jr z,.battleAnimationOn +.battleAnimationOff + set 7,d + jr .checkBattleStyle +.battleAnimationOn + res 7,d +.checkBattleStyle + ld a,[wOptionsBattleStyleCursorX] ; battle style cursor X coordinate + dec a + jr z,.battleStyleShift +.battleStyleSet + set 6,d + jr .storeOptions +.battleStyleShift + res 6,d +.storeOptions + ld a,d + ld [wOptions],a + ret + +; reads the options variable and places menu cursors in the correct positions within the options menu +SetCursorPositionsFromOptions: + ld hl,TextSpeedOptionData + 1 + ld a,[wOptions] + ld c,a + and a,$3f + push bc + ld de,2 + call IsInArray + pop bc + dec hl + ld a,[hl] + ld [wOptionsTextSpeedCursorX],a ; text speed cursor X coordinate + coord hl, 0, 3 + call .placeUnfilledRightArrow + sla c + ld a,1 ; On + jr nc,.storeBattleAnimationCursorX + ld a,10 ; Off +.storeBattleAnimationCursorX + ld [wOptionsBattleAnimCursorX],a ; battle animation cursor X coordinate + coord hl, 0, 8 + call .placeUnfilledRightArrow + sla c + ld a,1 + jr nc,.storeBattleStyleCursorX + ld a,10 +.storeBattleStyleCursorX + ld [wOptionsBattleStyleCursorX],a ; battle style cursor X coordinate + coord hl, 0, 13 + call .placeUnfilledRightArrow +; cursor in front of Cancel + coord hl, 0, 16 + ld a,1 +.placeUnfilledRightArrow + ld e,a + ld d,0 + add hl,de + ld [hl],$ec ; unfilled right arrow menu cursor + ret + +; table that indicates how the 3 text speed options affect frame delays +; Format: +; 00: X coordinate of menu cursor +; 01: delay after printing a letter (in frames) +TextSpeedOptionData: + db 14,5 ; Slow + db 7,3 ; Medium + db 1,1 ; Fast + db 7 ; default X coordinate (Medium) + db $ff ; terminator + +CheckForPlayerNameInSRAM: +; Check if the player name data in SRAM has a string terminator character +; (indicating that a name may have been saved there) and return whether it does +; in carry. + ld a, SRAM_ENABLE + ld [MBC1SRamEnable], a + ld a, $1 + ld [MBC1SRamBankingMode], a + ld [MBC1SRamBank], a + ld b, NAME_LENGTH + ld hl, sPlayerName +.loop + ld a, [hli] + cp "@" + jr z, .found + dec b + jr nz, .loop +; not found + xor a + ld [MBC1SRamEnable], a + ld [MBC1SRamBankingMode], a + and a + ret +.found + xor a + ld [MBC1SRamEnable], a + ld [MBC1SRamBankingMode], a + scf + ret diff --git a/de/engine/menu/naming_screen.asm b/de/engine/menu/naming_screen.asm new file mode 100755 index 00000000..a3c2c72a --- /dev/null +++ b/de/engine/menu/naming_screen.asm @@ -0,0 +1,512 @@ +AskName: + call SaveScreenTilesToBuffer1 + call GetPredefRegisters + push hl + ld a, [wIsInBattle] + dec a + coord hl, 0, 0 + ld b, 4 + ld c, 11 + call z, ClearScreenArea ; only if in wild battle + ld a, [wcf91] + ld [wd11e], a + call GetMonName + ld hl, DoYouWantToNicknameText + call PrintText + coord hl, 13, 7 + lb bc, 8, 14 + ld a, TWO_OPTION_MENU + ld [wTextBoxID], a + call DisplayTextBoxID + pop hl + ld a, [wCurrentMenuItem] + and a + jr nz, .declinedNickname + ld a, [wUpdateSpritesEnabled] + push af + xor a + ld [wUpdateSpritesEnabled], a + push hl + ld a, NAME_MON_SCREEN + ld [wNamingScreenType], a + call DisplayNamingScreen + ld a, [wIsInBattle] + and a + jr nz, .inBattle + call ReloadMapSpriteTilePatterns +.inBattle + call LoadScreenTilesFromBuffer1 + pop hl + pop af + ld [wUpdateSpritesEnabled], a + ld a, [wcf50] + cp "@" + ret nz +.declinedNickname + ld d, h + ld e, l + ld hl, wcd6d + ld bc, NAME_LENGTH + jp CopyData + +DoYouWantToNicknameText: + TX_FAR _DoYouWantToNicknameText + db "@" + +DisplayNameRaterScreen: + ld hl, wBuffer + xor a + ld [wUpdateSpritesEnabled], a + ld a, NAME_MON_SCREEN + ld [wNamingScreenType], a + call DisplayNamingScreen + call GBPalWhiteOutWithDelay3 + call RestoreScreenTilesAndReloadTilePatterns + call LoadGBPal + ld a, [wcf50] + cp "@" + jr z, .playerCancelled + ld hl, wPartyMonNicks + ld bc, NAME_LENGTH + ld a, [wWhichPokemon] + call AddNTimes + ld e, l + ld d, h + ld hl, wBuffer + ld bc, NAME_LENGTH + call CopyData + and a + ret +.playerCancelled + scf + ret + +DisplayNamingScreen: + push hl + ld hl, wd730 + set 6, [hl] + call GBPalWhiteOutWithDelay3 + call ClearScreen + call UpdateSprites + ld b, SET_PAL_GENERIC + call RunPaletteCommand + call LoadHpBarAndStatusTilePatterns + call LoadEDTile + callba LoadMonPartySpriteGfx + coord hl, 0, 4 + ld b, 9 + ld c, 18 + call TextBoxBorder + call PrintNamingText + ld a, 3 + ld [wTopMenuItemY], a + ld a, 1 + ld [wTopMenuItemX], a + ld [wLastMenuItem], a + ld [wCurrentMenuItem], a + ld a, $ff + ld [wMenuWatchedKeys], a + ld a, 7 + ld [wMaxMenuItem], a + ld a, "@" + ld [wcf50], a + xor a + ld hl, wNamingScreenSubmitName + ld [hli], a + ld [hli], a + ld [wAnimCounter], a +.selectReturnPoint + call PrintAlphabet + call GBPalNormal +.ABStartReturnPoint + ld a, [wNamingScreenSubmitName] + and a + jr nz, .submitNickname + call PrintNicknameAndUnderscores +.dPadReturnPoint + call PlaceMenuCursor +.inputLoop + ld a, [wCurrentMenuItem] + push af + callba AnimatePartyMon_ForceSpeed1 + pop af + ld [wCurrentMenuItem], a + call JoypadLowSensitivity + ld a, [hJoyPressed] + and a + jr z, .inputLoop + ld hl, .namingScreenButtonFunctions +.checkForPressedButton + sla a + jr c, .foundPressedButton + inc hl + inc hl + inc hl + inc hl + jr .checkForPressedButton +.foundPressedButton + ld a, [hli] + ld e, a + ld a, [hli] + ld d, a + ld a, [hli] + ld h, [hl] + ld l, a + push de + jp hl + +.submitNickname + pop de + ld hl, wcf50 + ld bc, NAME_LENGTH + call CopyData + call GBPalWhiteOutWithDelay3 + call ClearScreen + call ClearSprites + call RunDefaultPaletteCommand + call GBPalNormal + xor a + ld [wAnimCounter], a + ld hl, wd730 + res 6, [hl] + ld a, [wIsInBattle] + and a + jp z, LoadTextBoxTilePatterns + jpab LoadHudTilePatterns + +.namingScreenButtonFunctions + dw .dPadReturnPoint + dw .pressedDown + dw .dPadReturnPoint + dw .pressedUp + dw .dPadReturnPoint + dw .pressedLeft + dw .dPadReturnPoint + dw .pressedRight + dw .ABStartReturnPoint + dw .pressedStart + dw .selectReturnPoint + dw .pressedSelect + dw .ABStartReturnPoint + dw .pressedB + dw .ABStartReturnPoint + dw .pressedA + +.pressedA_changedCase + pop de + ld de, .selectReturnPoint + push de +.pressedSelect + ld a, [wAlphabetCase] + xor $1 + ld [wAlphabetCase], a + ret + +.pressedStart + ld a, 1 + ld [wNamingScreenSubmitName], a + ret + +.pressedA + ld a, [wCurrentMenuItem] + cp $5 ; "ED" row + jr nz, .didNotPressED + ld a, [wTopMenuItemX] + cp $11 ; "ED" column + jr z, .pressedStart +.didNotPressED + ld a, [wCurrentMenuItem] + cp $6 ; case switch row + jr nz, .didNotPressCaseSwtich + ld a, [wTopMenuItemX] + cp $1 ; case switch column + jr z, .pressedA_changedCase +.didNotPressCaseSwtich + ld hl, wMenuCursorLocation + ld a, [hli] + ld h, [hl] + ld l, a + inc hl + ld a, [hl] + ld [wNamingScreenLetter], a + call CalcStringLength + ld a, [wNamingScreenLetter] + cp $e5 + ld de, Dakutens + jr z, .dakutensAndHandakutens + cp $e4 + ld de, Handakutens + jr z, .dakutensAndHandakutens + ld a, [wNamingScreenType] + cp NAME_MON_SCREEN + jr nc, .checkMonNameLength + ld a, [wNamingScreenNameLength] + cp $7 ; max length of player/rival names + jr .checkNameLength +.checkMonNameLength + ld a, [wNamingScreenNameLength] + cp $a ; max length of pokemon nicknames +.checkNameLength + jr c, .addLetter + ret + +.dakutensAndHandakutens + push hl + call DakutensAndHandakutens + pop hl + ret nc + dec hl +.addLetter + ld a, [wNamingScreenLetter] + ld [hli], a + ld [hl], "@" + ld a, SFX_PRESS_AB + call PlaySound + ret +.pressedB + ld a, [wNamingScreenNameLength] + and a + ret z + call CalcStringLength + dec hl + ld [hl], "@" + ret +.pressedRight + ld a, [wCurrentMenuItem] + cp $6 + ret z ; can't scroll right on bottom row + ld a, [wTopMenuItemX] + cp $11 ; max + jp z, .wrapToFirstColumn + inc a + inc a + jr .done +.wrapToFirstColumn + ld a, $1 + jr .done +.pressedLeft + ld a, [wCurrentMenuItem] + cp $6 + ret z ; can't scroll right on bottom row + ld a, [wTopMenuItemX] + dec a + jp z, .wrapToLastColumn + dec a + jr .done +.wrapToLastColumn + ld a, $11 ; max + jr .done +.pressedUp + ld a, [wCurrentMenuItem] + dec a + ld [wCurrentMenuItem], a + and a + ret nz + ld a, $6 ; wrap to bottom row + ld [wCurrentMenuItem], a + ld a, $1 ; force left column + jr .done +.pressedDown + ld a, [wCurrentMenuItem] + inc a + ld [wCurrentMenuItem], a + cp $7 + jr nz, .wrapToTopRow + ld a, $1 + ld [wCurrentMenuItem], a + jr .done +.wrapToTopRow + cp $6 + ret nz + ld a, $1 +.done + ld [wTopMenuItemX], a + jp EraseMenuCursor + +LoadEDTile: + call DisableLCD + ld de, vFont + $700 + ld hl, ED_Tile + ld bc, (ED_TileEnd - ED_Tile) + ; to fix the graphical bug on poor emulators + ;lb bc, BANK(ED_Tile), (ED_TileEnd - ED_Tile) + ld a,$01 + call FarCopyDataDouble + jp EnableLCD + +ED_Tile: + INCBIN "gfx/ED_tile.1bpp" +ED_TileEnd: + +PrintAlphabet: + xor a + ld [H_AUTOBGTRANSFERENABLED], a + ld a, [wAlphabetCase] + and a + ld de, LowerCaseAlphabet + jr nz, .lowercase + ld de, UpperCaseAlphabet +.lowercase + coord hl, 2, 5 + lb bc, 5, 9 ; 5 rows, 9 columns +.outerLoop + push bc +.innerLoop + ld a, [de] + ld [hli], a + inc hl + inc de + dec c + jr nz, .innerLoop + ld bc, SCREEN_WIDTH + 2 + add hl, bc + pop bc + dec b + jr nz, .outerLoop + call PlaceString + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + jp Delay3 + +LowerCaseAlphabet: + db "abcdefghijklmnopqrstuvwxyz äöü:×()",$e1,$e2,"-?!♂♀/⠄,¥GROSSBUCHSTABEN@" + +UpperCaseAlphabet: + db "ABCDEFGHIJKLMNOPQRSTUVWXYZ ÄÖÜ:;[]",$e1,$e2,"-?!♂♀/⠄,¥kleinbuchstaben@" + +PrintNicknameAndUnderscores: + call CalcStringLength + ld a, c + ld [wNamingScreenNameLength], a + coord hl, 10, 2 + lb bc, 1, 10 + call ClearScreenArea + coord hl, 10, 2 + ld de, wcf50 + call PlaceString + coord hl, 10, 3 + ld a, [wNamingScreenType] + cp NAME_MON_SCREEN + jr nc, .pokemon1 + ld b, 7 ; player or rival max name length + jr .playerOrRival1 +.pokemon1 + ld b, 10 ; pokemon max name length +.playerOrRival1 + ld a, $76 ; underscore tile id +.placeUnderscoreLoop + ld [hli], a + dec b + jr nz, .placeUnderscoreLoop + ld a, [wNamingScreenType] + cp NAME_MON_SCREEN + ld a, [wNamingScreenNameLength] + jr nc, .pokemon2 + cp 7 ; player or rival max name length + jr .playerOrRival2 +.pokemon2 + cp 10 ; pokemon max name length +.playerOrRival2 + jr nz, .emptySpacesRemaining + ; when all spaces are filled, force the cursor onto the ED tile + call EraseMenuCursor + ld a, $11 ; "ED" x coord + ld [wTopMenuItemX], a + ld a, $5 ; "ED" y coord + ld [wCurrentMenuItem], a + ld a, [wNamingScreenType] + cp NAME_MON_SCREEN + ld a, 9 ; keep the last underscore raised + jr nc, .pokemon3 + ld a, 6 ; keep the last underscore raised +.pokemon3 +.emptySpacesRemaining + ld c, a + ld b, $0 + coord hl, 10, 3 + add hl, bc + ld [hl], $77 ; raised underscore tile id + ret + +DakutensAndHandakutens: + push de + call CalcStringLength + dec hl + ld a, [hl] + pop hl + ld de, $2 + call IsInArray + ret nc + inc hl + ld a, [hl] + ld [wNamingScreenLetter], a + ret + +Dakutens: + db "かが", "きぎ", "くぐ", "けげ", "こご" + db "さざ", "しじ", "すず", "せぜ", "そぞ" + db "ただ", "ちぢ", "つづ", "てで", "とど" + db "はば", "ひび", "ふぶ", "へべ", "ほぼ" + db "カガ", "キギ", "クグ", "ケゲ", "コゴ" + db "サザ", "シジ", "スズ", "セゼ", "ソゾ" + db "タダ", "チヂ", "ツヅ", "テデ", "トド" + db "ハバ", "ヒビ", "フブ", "へべ", "ホボ" + db $ff + +Handakutens: + db "はぱ", "ひぴ", "ふぷ", "へぺ", "ほぽ" + db "ハパ", "ヒピ", "フプ", "へぺ", "ホポ" + db $ff + +; calculates the length of the string at wcf50 and stores it in c +CalcStringLength: + ld hl, wcf50 + ld c, $0 +.loop + ld a, [hl] + cp "@" + ret z + inc hl + inc c + jr .loop + +PrintNamingText: + coord hl, 0, 1 + ld a, [wNamingScreenType] + ld de, YourTextString + and a + jr z, .notNickname + ld de, RivalsTextString + dec a + jr z, .notNickname + ld a, [wcf91] + ld [wMonPartySpriteSpecies], a + push af + callba WriteMonPartySpriteOAMBySpecies + pop af + ld [wd11e], a + call GetMonName + coord hl, 4, 1 + call PlaceString + coord hl, 1, 3 + ld de, NicknameTextString + jr .placeString +.notNickname + call PlaceString + ld l, c + ld h, b + ld de, NameTextString +.placeString + jp PlaceString + +YourTextString: + db "DEIN @" + +RivalsTextString: + db "GEGNER-@" + +NameTextString: + db "NAME?@" + +NicknameTextString: + db "ALIAS?@" diff --git a/de/engine/menu/party_menu.asm b/de/engine/menu/party_menu.asm new file mode 100755 index 00000000..ff302968 --- /dev/null +++ b/de/engine/menu/party_menu.asm @@ -0,0 +1,325 @@ +; [wPartyMenuTypeOrMessageID] = menu type / message ID +; if less than $F0, it is a menu type +; menu types: +; 00: normal pokemon menu (e.g. Start menu) +; 01: use healing item on pokemon menu +; 02: in-battle switch pokemon menu +; 03: learn TM/HM menu +; 04: swap pokemon positions menu +; 05: use evolution stone on pokemon menu +; otherwise, it is a message ID +; f0: poison healed +; f1: burn healed +; f2: freeze healed +; f3: sleep healed +; f4: paralysis healed +; f5: HP healed +; f6: health returned +; f7: revitalized +; f8: leveled up +DrawPartyMenu_: + xor a + ld [H_AUTOBGTRANSFERENABLED],a + call ClearScreen + call UpdateSprites + callba LoadMonPartySpriteGfxWithLCDDisabled ; load pokemon icon graphics + +RedrawPartyMenu_: + ld a,[wPartyMenuTypeOrMessageID] + cp a,SWAP_MONS_PARTY_MENU + jp z,.printMessage + call ErasePartyMenuCursors + callba InitPartyMenuBlkPacket + coord hl, 3, 0 + ld de,wPartySpecies + xor a + ld c,a + ld [hPartyMonIndex],a + ld [wWhichPartyMenuHPBar],a +.loop + ld a,[de] + cp a,$FF ; reached the terminator? + jp z,.afterDrawingMonEntries + push bc + push de + push hl + ld a,c + push hl + ld hl,wPartyMonNicks + call GetPartyMonName + pop hl + call PlaceString ; print the pokemon's name + callba WriteMonPartySpriteOAMByPartyIndex ; place the appropriate pokemon icon + ld a,[hPartyMonIndex] + ld [wWhichPokemon],a + inc a + ld [hPartyMonIndex],a + call LoadMonData + pop hl + push hl + ld a,[wMenuItemToSwap] + and a ; is the player swapping pokemon positions? + jr z,.skipUnfilledRightArrow +; if the player is swapping pokemon positions + dec a + ld b,a + ld a,[wWhichPokemon] + cp b ; is the player swapping the current pokemon in the list? + jr nz,.skipUnfilledRightArrow +; the player is swapping the current pokemon in the list + dec hl + dec hl + dec hl + ld a,"▷" ; unfilled right arrow menu cursor + ld [hli],a ; place the cursor + inc hl + inc hl +.skipUnfilledRightArrow + ld a,[wPartyMenuTypeOrMessageID] ; menu type + cp a,TMHM_PARTY_MENU + jr z,.teachMoveMenu + cp a,EVO_STONE_PARTY_MENU + jr z,.evolutionStoneMenu + push hl + ld bc,14 ; 14 columns to the right + add hl,bc + ld de,wLoadedMonStatus + call PrintStatusCondition + pop hl + push hl + ld bc,SCREEN_WIDTH + 1 ; down 1 row and right 1 column + ld a,[hFlags_0xFFF6] + set 0,a + ld [hFlags_0xFFF6],a + add hl,bc + predef DrawHP2 ; draw HP bar and prints current / max HP + ld a,[hFlags_0xFFF6] + res 0,a + ld [hFlags_0xFFF6],a + call SetPartyMenuHPBarColor ; color the HP bar (on SGB) + pop hl + jr .printLevel +.teachMoveMenu + push hl + predef CanLearnTM ; check if the pokemon can learn the move + pop hl + ld de,.ableToLearnMoveText + ld a,c + and a + jr nz,.placeMoveLearnabilityString + ld de,.notAbleToLearnMoveText +.placeMoveLearnabilityString + ld bc,20 + 9 ; down 1 row and right 9 columns + push hl + add hl,bc + call PlaceString + pop hl +.printLevel + ld bc,10 ; move 10 columns to the right + add hl,bc + call PrintLevel + pop hl + pop de + inc de + ld bc,2 * 20 + add hl,bc + pop bc + inc c + jp .loop +.ableToLearnMoveText + db "OK@" +.notAbleToLearnMoveText + db "NEIN@" +.evolutionStoneMenu + push hl + ld hl,EvosMovesPointerTable + ld b,0 + ld a,[wLoadedMonSpecies] + dec a + add a + rl b + ld c,a + add hl,bc + ld de,wcd6d + ld a,BANK(EvosMovesPointerTable) + ld bc,2 + call FarCopyData + ld hl,wcd6d + ld a,[hli] + ld h,[hl] + ld l,a + ld de,wcd6d + ld a,BANK(EvosMovesPointerTable) + ld bc,Mon133_EvosEnd - Mon133_EvosMoves + call FarCopyData + ld hl,wcd6d + ld de,.notAbleToEvolveText +; loop through the pokemon's evolution entries +.checkEvolutionsLoop + ld a,[hli] + and a ; reached terminator? + jr z,.placeEvolutionStoneString ; if so, place the "NOT ABLE" string + inc hl + inc hl + cp a,EV_ITEM + jr nz,.checkEvolutionsLoop +; if it's a stone evolution entry + dec hl + dec hl + ld b,[hl] + ld a,[wEvoStoneItemID] ; the stone the player used + inc hl + inc hl + inc hl + cp b ; does the player's stone match this evolution entry's stone? + jr nz,.checkEvolutionsLoop +; if it does match + ld de,.ableToEvolveText +.placeEvolutionStoneString + ld bc,20 + 9 ; down 1 row and right 9 columns + pop hl + push hl + add hl,bc + call PlaceString + pop hl + jr .printLevel +.ableToEvolveText + db "OK@" +.notAbleToEvolveText + db "NEIN@" +.afterDrawingMonEntries + ld b, SET_PAL_PARTY_MENU + call RunPaletteCommand +.printMessage + ld hl,wd730 + ld a,[hl] + push af + push hl + set 6,[hl] ; turn off letter printing delay + ld a,[wPartyMenuTypeOrMessageID] ; message ID + cp a,$F0 + jr nc,.printItemUseMessage + add a + ld hl,PartyMenuMessagePointers + ld b,0 + ld c,a + add hl,bc + ld a,[hli] + ld h,[hl] + ld l,a + call PrintText +.done + pop hl + pop af + ld [hl],a + ld a,1 + ld [H_AUTOBGTRANSFERENABLED],a + call Delay3 + jp GBPalNormal +.printItemUseMessage + and a,$0F + ld hl,PartyMenuItemUseMessagePointers + add a + ld c,a + ld b,0 + add hl,bc + ld a,[hli] + ld h,[hl] + ld l,a + push hl + ld a,[wUsedItemOnWhichPokemon] + ld hl,wPartyMonNicks + call GetPartyMonName + pop hl + call PrintText + jr .done + +PartyMenuItemUseMessagePointers: + dw AntidoteText + dw BurnHealText + dw IceHealText + dw AwakeningText + dw ParlyzHealText + dw PotionText + dw FullHealText + dw ReviveText + dw RareCandyText + +PartyMenuMessagePointers: + dw PartyMenuNormalText + dw PartyMenuItemUseText + dw PartyMenuBattleText + dw PartyMenuUseTMText + dw PartyMenuSwapMonText + dw PartyMenuItemUseText + +PartyMenuNormalText: + TX_FAR _PartyMenuNormalText + db "@" + +PartyMenuItemUseText: + TX_FAR _PartyMenuItemUseText + db "@" + +PartyMenuBattleText: + TX_FAR _PartyMenuBattleText + db "@" + +PartyMenuUseTMText: + TX_FAR _PartyMenuUseTMText + db "@" + +PartyMenuSwapMonText: + TX_FAR _PartyMenuSwapMonText + db "@" + +PotionText: + TX_FAR _PotionText + db "@" + +AntidoteText: + TX_FAR _AntidoteText + db "@" + +ParlyzHealText: + TX_FAR _ParlyzHealText + db "@" + +BurnHealText: + TX_FAR _BurnHealText + db "@" + +IceHealText: + TX_FAR _IceHealText + db "@" + +AwakeningText: + TX_FAR _AwakeningText + db "@" + +FullHealText: + TX_FAR _FullHealText + db "@" + +ReviveText: + TX_FAR _ReviveText + db "@" + +RareCandyText: + TX_FAR _RareCandyText + TX_SFX_ITEM_1 ; probably supposed to play SFX_LEVEL_UP but the wrong music bank is loaded + TX_BLINK + db "@" + +SetPartyMenuHPBarColor: + ld hl, wPartyMenuHPBarColors + ld a, [wWhichPartyMenuHPBar] + ld c, a + ld b, 0 + add hl, bc + call GetHealthBarColor + ld b, UPDATE_PARTY_MENU_BLK_PACKET + call RunPaletteCommand + ld hl, wWhichPartyMenuHPBar + inc [hl] + ret diff --git a/de/engine/menu/players_pc.asm b/de/engine/menu/players_pc.asm new file mode 100755 index 00000000..c5acfec7 --- /dev/null +++ b/de/engine/menu/players_pc.asm @@ -0,0 +1,303 @@ +PlayerPC: + ld a, ITEM_NAME + ld [wNameListType], a + call SaveScreenTilesToBuffer1 + xor a + ld [wBagSavedMenuItem], a + ld [wParentMenuItem], a + ld a, [wFlags_0xcd60] + bit 3, a ; accessing player's PC through another PC? + jr nz, PlayerPCMenu +; accessing it directly + ld a, SFX_TURN_ON_PC + call PlaySound + ld hl, TurnedOnPC2Text + call PrintText + +PlayerPCMenu: + ld hl, wd730 + set 6, [hl] + ld a, [wParentMenuItem] + ld [wCurrentMenuItem], a + ld hl, wFlags_0xcd60 + set 5, [hl] + call LoadScreenTilesFromBuffer2 + coord hl, 0, 0 + ld b, 8 + ld c, 15 + call TextBoxBorder + call UpdateSprites + coord hl, 2, 2 + ld de, PlayersPCMenuEntries + call PlaceString + ld hl, wTopMenuItemY + ld a, 2 + ld [hli], a ; wTopMenuItemY + dec a + ld [hli], a ; wTopMenuItemX + inc hl + inc hl + ld a, 3 + ld [hli], a ; wMaxMenuItem + ld a, A_BUTTON | B_BUTTON + ld [hli], a ; wMenuWatchedKeys + xor a + ld [hl], a + ld hl, wListScrollOffset + ld [hli], a ; wListScrollOffset + ld [hl], a ; wMenuWatchMovingOutOfBounds + ld [wPlayerMonNumber], a + ld hl, WhatDoYouWantText + call PrintText + call HandleMenuInput + bit 1, a + jp nz, ExitPlayerPC + call PlaceUnfilledArrowMenuCursor + ld a, [wCurrentMenuItem] + ld [wParentMenuItem], a + and a + jp z, PlayerPCWithdraw + dec a + jp z, PlayerPCDeposit + dec a + jp z, PlayerPCToss + +ExitPlayerPC: + ld a, [wFlags_0xcd60] + bit 3, a ; accessing player's PC through another PC? + jr nz, .next +; accessing it directly + ld a, SFX_TURN_OFF_PC + call PlaySound + call WaitForSoundToFinish +.next + ld hl, wFlags_0xcd60 + res 5, [hl] + call LoadScreenTilesFromBuffer2 + xor a + ld [wListScrollOffset], a + ld [wBagSavedMenuItem], a + ld hl, wd730 + res 6, [hl] + xor a + ld [wDoNotWaitForButtonPressAfterDisplayingText], a + ret + +PlayerPCDeposit: + xor a + ld [wCurrentMenuItem], a + ld [wListScrollOffset], a + ld a, [wNumBagItems] + and a + jr nz, .loop + ld hl, NothingToDepositText + call PrintText + jp PlayerPCMenu +.loop + ld hl, WhatToDepositText + call PrintText + ld hl, wNumBagItems + ld a, l + ld [wListPointer], a + ld a, h + ld [wListPointer + 1], a + xor a + ld [wPrintItemPrices], a + ld a, ITEMLISTMENU + ld [wListMenuID], a + call DisplayListMenuID + jp c, PlayerPCMenu + call IsKeyItem + ld a, 1 + ld [wItemQuantity], a + ld a, [wIsKeyItem] + and a + jr nz, .next +; if it's not a key item, there can be more than one of the item + ld hl, DepositHowManyText + call PrintText + call DisplayChooseQuantityMenu + cp $ff + jp z, .loop +.next + ld hl, wNumBoxItems + call AddItemToInventory + jr c, .roomAvailable + ld hl, NoRoomToStoreText + call PrintText + jp .loop +.roomAvailable + ld hl, wNumBagItems + call RemoveItemFromInventory + call WaitForSoundToFinish + ld a, SFX_WITHDRAW_DEPOSIT + call PlaySound + call WaitForSoundToFinish + ld hl, ItemWasStoredText + call PrintText + jp .loop + +PlayerPCWithdraw: + xor a + ld [wCurrentMenuItem], a + ld [wListScrollOffset], a + ld a, [wNumBoxItems] + and a + jr nz, .loop + ld hl, NothingStoredText + call PrintText + jp PlayerPCMenu +.loop + ld hl, WhatToWithdrawText + call PrintText + ld hl, wNumBoxItems + ld a, l + ld [wListPointer], a + ld a, h + ld [wListPointer + 1], a + xor a + ld [wPrintItemPrices], a + ld a, ITEMLISTMENU + ld [wListMenuID], a + call DisplayListMenuID + jp c, PlayerPCMenu + call IsKeyItem + ld a, 1 + ld [wItemQuantity], a + ld a, [wIsKeyItem] + and a + jr nz, .next +; if it's not a key item, there can be more than one of the item + ld hl, WithdrawHowManyText + call PrintText + call DisplayChooseQuantityMenu + cp $ff + jp z, .loop +.next + ld hl, wNumBagItems + call AddItemToInventory + jr c, .roomAvailable + ld hl, CantCarryMoreText + call PrintText + jp .loop +.roomAvailable + ld hl, wNumBoxItems + call RemoveItemFromInventory + call WaitForSoundToFinish + ld a, SFX_WITHDRAW_DEPOSIT + call PlaySound + call WaitForSoundToFinish + ld hl, WithdrewItemText + call PrintText + jp .loop + +PlayerPCToss: + xor a + ld [wCurrentMenuItem], a + ld [wListScrollOffset], a + ld a, [wNumBoxItems] + and a + jr nz, .loop + ld hl, NothingStoredText + call PrintText + jp PlayerPCMenu +.loop + ld hl, WhatToTossText + call PrintText + ld hl, wNumBoxItems + ld a, l + ld [wListPointer], a + ld a, h + ld [wListPointer + 1], a + xor a + ld [wPrintItemPrices], a + ld a, ITEMLISTMENU + ld [wListMenuID], a + push hl + call DisplayListMenuID + pop hl + jp c, PlayerPCMenu + push hl + call IsKeyItem + pop hl + ld a, 1 + ld [wItemQuantity], a + ld a, [wIsKeyItem] + and a + jr nz, .next + ld a, [wcf91] + call IsItemHM + jr c, .next +; if it's not a key item, there can be more than one of the item + push hl + ld hl, TossHowManyText + call PrintText + call DisplayChooseQuantityMenu + pop hl + cp $ff + jp z, .loop +.next + call TossItem ; disallows tossing key items + jp .loop + +PlayersPCMenuEntries: + db "ITEM AUFNEHMEN" + next "ITEM ABLEGEN" + next "ITEM WEGWERFEN" + next "AUSLOGGEN@" + +TurnedOnPC2Text: + TX_FAR _TurnedOnPC2Text + db "@" + +WhatDoYouWantText: + TX_FAR _WhatDoYouWantText + db "@" + +WhatToDepositText: + TX_FAR _WhatToDepositText + db "@" + +DepositHowManyText: + TX_FAR _DepositHowManyText + db "@" + +ItemWasStoredText: + TX_FAR _ItemWasStoredText + db "@" + +NothingToDepositText: + TX_FAR _NothingToDepositText + db "@" + +NoRoomToStoreText: + TX_FAR _NoRoomToStoreText + db "@" + +WhatToWithdrawText: + TX_FAR _WhatToWithdrawText + db "@" + +WithdrawHowManyText: + TX_FAR _WithdrawHowManyText + db "@" + +WithdrewItemText: + TX_FAR _WithdrewItemText + db "@" + +NothingStoredText: + TX_FAR _NothingStoredText + db "@" + +CantCarryMoreText: + TX_FAR _CantCarryMoreText + db "@" + +WhatToTossText: + TX_FAR _WhatToTossText + db "@" + +TossHowManyText: + TX_FAR _TossHowManyText + db "@" diff --git a/de/engine/menu/pokedex.asm b/de/engine/menu/pokedex.asm new file mode 100755 index 00000000..ea28c133 --- /dev/null +++ b/de/engine/menu/pokedex.asm @@ -0,0 +1,666 @@ +ShowPokedexMenu: + call GBPalWhiteOut + call ClearScreen + call UpdateSprites + ld a,[wListScrollOffset] + push af + xor a + ld [wCurrentMenuItem],a + ld [wListScrollOffset],a + ld [wLastMenuItem],a + inc a + ld [wd11e],a + ld [hJoy7],a +.setUpGraphics + ld b, SET_PAL_GENERIC + call RunPaletteCommand + callab LoadPokedexTilePatterns +.doPokemonListMenu + ld hl,wTopMenuItemY + ld a,3 + ld [hli],a ; top menu item Y + xor a + ld [hli],a ; top menu item X + inc a + ld [wMenuWatchMovingOutOfBounds],a + inc hl + inc hl + ld a,6 + ld [hli],a ; max menu item ID + ld [hl],D_LEFT | D_RIGHT | B_BUTTON | A_BUTTON + call HandlePokedexListMenu + jr c,.goToSideMenu ; if the player chose a pokemon from the list +.exitPokedex + xor a + ld [wMenuWatchMovingOutOfBounds],a + ld [wCurrentMenuItem],a + ld [wLastMenuItem],a + ld [hJoy7],a + ld [wWastedByteCD3A],a + ld [wOverrideSimulatedJoypadStatesMask],a + pop af + ld [wListScrollOffset],a + call GBPalWhiteOutWithDelay3 + call RunDefaultPaletteCommand + jp ReloadMapData +.goToSideMenu + call HandlePokedexSideMenu + dec b + jr z,.exitPokedex ; if the player chose Quit + dec b + jr z,.doPokemonListMenu ; if pokemon not seen or player pressed B button + jp .setUpGraphics ; if pokemon data or area was shown + +; handles the menu on the lower right in the pokedex screen +; OUTPUT: +; b = reason for exiting menu +; 00: showed pokemon data or area +; 01: the player chose Quit +; 02: the pokemon has not been seen yet or the player pressed the B button +HandlePokedexSideMenu: + call PlaceUnfilledArrowMenuCursor + ld a,[wCurrentMenuItem] + push af + ld b,a + ld a,[wLastMenuItem] + push af + ld a,[wListScrollOffset] + push af + add b + inc a + ld [wd11e],a + ld a,[wd11e] + push af + ld a,[wDexMaxSeenMon] + push af ; this doesn't need to be preserved + ld hl,wPokedexSeen + call IsPokemonBitSet + ld b,2 + jr z,.exitSideMenu + call PokedexToIndex + ld hl,wTopMenuItemY + ld a,10 + ld [hli],a ; top menu item Y + ld a,15 + ld [hli],a ; top menu item X + xor a + ld [hli],a ; current menu item ID + inc hl + ld a,3 + ld [hli],a ; max menu item ID + ;ld a, A_BUTTON | B_BUTTON + ld [hli],a ; menu watched keys (A button and B button) + xor a + ld [hli],a ; old menu item ID + ld [wMenuWatchMovingOutOfBounds],a +.handleMenuInput + call HandleMenuInput + bit 1,a ; was the B button pressed? + ld b,2 + jr nz,.buttonBPressed + ld a,[wCurrentMenuItem] + and a + jr z,.choseData + dec a + jr z,.choseCry + dec a + jr z,.choseArea +.choseQuit + ld b,1 +.exitSideMenu + pop af + ld [wDexMaxSeenMon],a + pop af + ld [wd11e],a + pop af + ld [wListScrollOffset],a + pop af + ld [wLastMenuItem],a + pop af + ld [wCurrentMenuItem],a + push bc + coord hl, 0, 3 + ld de,20 + lb bc, " ", 13 + call DrawTileLine ; cover up the menu cursor in the pokemon list + pop bc + ret + +.buttonBPressed + push bc + coord hl, 15, 10 + ld de,20 + lb bc, " ", 7 + call DrawTileLine ; cover up the menu cursor in the side menu + pop bc + jr .exitSideMenu + +.choseData + call ShowPokedexDataInternal + ld b,0 + jr .exitSideMenu + +; play pokemon cry +.choseCry + ld a,[wd11e] + call GetCryData + call PlaySound + jr .handleMenuInput + +.choseArea + predef LoadTownMap_Nest ; display pokemon areas + ld b,0 + jr .exitSideMenu + +; handles the list of pokemon on the left of the pokedex screen +; sets carry flag if player presses A, unsets carry flag if player presses B +HandlePokedexListMenu: + xor a + ld [H_AUTOBGTRANSFERENABLED],a +; draw the horizontal line separating the seen and owned amounts from the menu + coord hl, 15, 8 + ld a,"─" + ld [hli],a + ld [hli],a + ld [hli],a + ld [hli],a + ld [hli],a + coord hl, 14, 0 + ld [hl],$71 ; vertical line tile + coord hl, 14, 1 + call DrawPokedexVerticalLine + coord hl, 14, 9 + call DrawPokedexVerticalLine + ld hl,wPokedexSeen + ld b,wPokedexSeenEnd - wPokedexSeen + call CountSetBits + ld de, wNumSetBits + coord hl, 16, 3 + lb bc, 1, 3 + call PrintNumber ; print number of seen pokemon + ld hl,wPokedexOwned + ld b,wPokedexOwnedEnd - wPokedexOwned + call CountSetBits + ld de, wNumSetBits + coord hl, 16, 6 + lb bc, 1, 3 + call PrintNumber ; print number of owned pokemon + coord hl, 16, 2 + ld de,PokedexSeenText + call PlaceString + coord hl, 16, 5 + ld de,PokedexOwnText + call PlaceString + coord hl, 1, 1 + ld de,PokedexContentsText + call PlaceString + coord hl, 16, 10 + ld de,PokedexMenuItemsText + call PlaceString +; find the highest pokedex number among the pokemon the player has seen + ld hl,wPokedexSeenEnd - 1 + ld b,(wPokedexSeenEnd - wPokedexSeen) * 8 + 1 +.maxSeenPokemonLoop + ld a,[hld] + ld c,8 +.maxSeenPokemonInnerLoop + dec b + sla a + jr c,.storeMaxSeenPokemon + dec c + jr nz,.maxSeenPokemonInnerLoop + jr .maxSeenPokemonLoop + +.storeMaxSeenPokemon + ld a,b + ld [wDexMaxSeenMon],a +.loop + xor a + ld [H_AUTOBGTRANSFERENABLED],a + coord hl, 4, 2 + lb bc, 14, 10 + call ClearScreenArea + coord hl, 1, 3 + ld a,[wListScrollOffset] + ld [wd11e],a + ld d,7 + ld a,[wDexMaxSeenMon] + cp a,7 + jr nc,.printPokemonLoop + ld d,a + dec a + ld [wMaxMenuItem],a +; loop to print pokemon pokedex numbers and names +; if the player has owned the pokemon, it puts a pokeball beside the name +.printPokemonLoop + ld a,[wd11e] + inc a + ld [wd11e],a + push af + push de + push hl + ld de,-SCREEN_WIDTH + add hl,de + ld de,wd11e + lb bc, LEADING_ZEROES | 1, 3 + call PrintNumber ; print the pokedex number + ld de,SCREEN_WIDTH + add hl,de + dec hl + push hl + ld hl,wPokedexOwned + call IsPokemonBitSet + pop hl + ld a," " + jr z,.writeTile + ld a,$72 ; pokeball tile +.writeTile + ld [hl],a ; put a pokeball next to pokemon that the player has owned + push hl + ld hl,wPokedexSeen + call IsPokemonBitSet + jr nz,.getPokemonName ; if the player has seen the pokemon + ld de,.dashedLine ; print a dashed line in place of the name if the player hasn't seen the pokemon + jr .skipGettingName +.dashedLine ; for unseen pokemon in the list + db "----------@" +.getPokemonName + call PokedexToIndex + call GetMonName +.skipGettingName + pop hl + inc hl + call PlaceString + pop hl + ld bc,2 * SCREEN_WIDTH + add hl,bc + pop de + pop af + ld [wd11e],a + dec d + jr nz,.printPokemonLoop + ld a,01 + ld [H_AUTOBGTRANSFERENABLED],a + call Delay3 + call GBPalNormal + call HandleMenuInput + bit 1,a ; was the B button pressed? + jp nz,.buttonBPressed +.checkIfUpPressed + bit 6,a ; was Up pressed? + jr z,.checkIfDownPressed +.upPressed ; scroll up one row + ld a,[wListScrollOffset] + and a + jp z,.loop + dec a + ld [wListScrollOffset],a + jp .loop +.checkIfDownPressed + bit 7,a ; was Down pressed? + jr z,.checkIfRightPressed +.downPressed ; scroll down one row + ld a,[wDexMaxSeenMon] + cp a,7 + jp c,.loop ; can't if the list is shorter than 7 + sub a,7 + ld b,a + ld a,[wListScrollOffset] + cp b + jp z,.loop + inc a + ld [wListScrollOffset],a + jp .loop +.checkIfRightPressed + bit 4,a ; was Right pressed? + jr z,.checkIfLeftPressed +.rightPressed ; scroll down 7 rows + ld a,[wDexMaxSeenMon] + cp a,7 + jp c,.loop ; can't if the list is shorter than 7 + sub a,6 + ld b,a + ld a,[wListScrollOffset] + add a,7 + ld [wListScrollOffset],a + cp b + jp c,.loop + dec b + ld a,b + ld [wListScrollOffset],a + jp .loop +.checkIfLeftPressed ; scroll up 7 rows + bit 5,a ; was Left pressed? + jr z,.buttonAPressed +.leftPressed + ld a,[wListScrollOffset] + sub a,7 + ld [wListScrollOffset],a + jp nc,.loop + xor a + ld [wListScrollOffset],a + jp .loop +.buttonAPressed + scf + ret +.buttonBPressed + and a + ret + +DrawPokedexVerticalLine: + ld c,9 ; height of line + ld de,SCREEN_WIDTH + ld a,$71 ; vertical line tile +.loop + ld [hl],a + add hl,de + xor a,1 ; toggle between vertical line tile and box tile + dec c + jr nz,.loop + ret + +PokedexSeenText: + db "GES@" + +PokedexOwnText: + db "BES@" + +PokedexContentsText: + db "INHALT@" + +PokedexMenuItemsText: + db "DATA" + next "RUF" + next "GEB." + next "ZUR.@" + +; tests if a pokemon's bit is set in the seen or owned pokemon bit fields +; INPUT: +; [wd11e] = pokedex number +; hl = address of bit field +IsPokemonBitSet: + ld a,[wd11e] + dec a + ld c,a + ld b,FLAG_TEST + predef FlagActionPredef + ld a,c + and a + ret + +; function to display pokedex data from outside the pokedex +ShowPokedexData: + call GBPalWhiteOutWithDelay3 + call ClearScreen + call UpdateSprites + callab LoadPokedexTilePatterns ; load pokedex tiles + +; function to display pokedex data from inside the pokedex +ShowPokedexDataInternal: + ld hl,wd72c + set 1,[hl] + ld a,$33 ; 3/7 volume + ld [rNR50],a + call GBPalWhiteOut ; zero all palettes + call ClearScreen + ld a,[wd11e] ; pokemon ID + ld [wcf91],a + push af + ld b, SET_PAL_POKEDEX + call RunPaletteCommand + pop af + ld [wd11e],a + ld a,[hTilesetType] + push af + xor a + ld [hTilesetType],a + + coord hl, 0, 0 + ld de,1 + lb bc, $64, SCREEN_WIDTH + call DrawTileLine ; draw top border + + coord hl, 0, 17 + ld b, $6f + call DrawTileLine ; draw bottom border + + coord hl, 0, 1 + ld de,20 + lb bc, $66, $10 + call DrawTileLine ; draw left border + + coord hl, 19, 1 + ld b,$67 + call DrawTileLine ; draw right border + + ld a,$63 ; upper left corner tile + Coorda 0, 0 + ld a,$65 ; upper right corner tile + Coorda 19, 0 + ld a,$6c ; lower left corner tile + Coorda 0, 17 + ld a,$6e ; lower right corner tile + Coorda 19, 17 + + coord hl, 0, 9 + ld de,PokedexDataDividerLine + call PlaceString ; draw horizontal divider line + + coord hl, 9, 6 + ld de,HeightWeightText + call PlaceString + + call GetMonName + coord hl, 9, 2 + call PlaceString + + ld hl,PokedexEntryPointers + ld a,[wd11e] + dec a + ld e,a + ld d,0 + add hl,de + add hl,de + ld a,[hli] + ld e,a + ld d,[hl] ; de = address of pokedex entry + + coord hl, 9, 4 + call PlaceString ; print species name + + ld h,b + ld l,c + push de + ld a,[wd11e] + push af + call IndexToPokedex + + coord hl, 2, 8 + ld a, "№" + ld [hli],a + ld a,"⠄" + ld [hli],a + ld de,wd11e + lb bc, LEADING_ZEROES | 1, 3 + call PrintNumber ; print pokedex number + + ld hl,wPokedexOwned + call IsPokemonBitSet + pop af + ld [wd11e],a + ld a,[wcf91] + ld [wd0b5],a + pop de + + push af + push bc + push de + push hl + + call Delay3 + call GBPalNormal + call GetMonHeader ; load pokemon picture location + coord hl, 1, 1 + call LoadFlippedFrontSpriteByMonIndex ; draw pokemon picture + ld a,[wcf91] + call PlayCry ; play pokemon cry + + pop hl + pop de + pop bc + pop af + + ld a,c + and a + jp z,.waitForButtonPress ; if the pokemon has not been owned, don't print the height, weight, or description + inc de ; de = address of feet (height) + ld a,[de] ; reads feet, but a is overwritten without being used + push af + coord hl, 13, 6 + lb bc, 1, 3 + call PrintNumber ; print feet (height) + ld hl, $C426 + pop af + cp $a + jr nc, .func_43d7 + ld [hl], $F6 +.func_43d7 + inc hl + ld a, [hli] + ldd [hl], a + ld [hl], $F2 + inc de + inc de + inc de ; de = address of inches (height) + push de +; put weight in big-endian order at hDexWeight + ld hl,hDexWeight + ld a,[hl] ; save existing value of [hDexWeight] + push af + ld a,[de] ; a = upper byte of weight + ld [hli],a ; store upper byte of weight in [hDexWeight] + ld a,[hl] ; save existing value of [hDexWeight + 1] + push af + dec de + ld a,[de] ; a = lower byte of weight + ld [hl],a ; store lower byte of weight in [hDexWeight + 1] + ld de,hDexWeight + coord hl, 12, 8 + lb bc, 2, 4 ; 2 bytes, 4 digits + call PrintNumber ; print weight + coord hl, 14, 8 + ld a,[hDexWeight + 1] + sub a,10 + ld a,[hDexWeight] + sbc a,0 + jr nc,.next + ld [hl],"0" ; if the weight is less than 10, put a 0 before the decimal point +.next + inc hl + ld a,[hli] + ld [hld],a ; make space for the decimal point by moving the last digit forward one tile + ld [hl],"⠄" ; decimal point tile + pop af + ld [hDexWeight + 1],a ; restore original value of [hDexWeight + 1] + pop af + ld [hDexWeight],a ; restore original value of [hDexWeight] + pop hl + inc hl ; hl = address of pokedex description text + coord bc, 1, 11 + ld a,2 + ld [$fff4],a + call TextCommandProcessor ; print pokedex description text + xor a + ld [$fff4],a +.waitForButtonPress + call JoypadLowSensitivity + ld a,[hJoy5] + and a,A_BUTTON | B_BUTTON + jr z,.waitForButtonPress + pop af + ld [hTilesetType],a + call GBPalWhiteOut + call ClearScreen + call RunDefaultPaletteCommand + call LoadTextBoxTilePatterns + call GBPalNormal + ld hl,wd72c + res 1,[hl] + ld a,$77 ; max volume + ld [rNR50],a + ret + +HeightWeightText: + db "GR. ???",$60 + next "GEW ???",$61,$62,"@" + +; XXX does anything point to this? +PokeText: + db "#@" + +; horizontal line that divides the pokedex text description from the rest of the data +PokedexDataDividerLine: + db $68,$69,$6B,$69,$6B + db $69,$6B,$69,$6B,$6B + db $6B,$6B,$69,$6B,$69 + db $6B,$69,$6B,$69,$6A + db "@" + +; draws a line of tiles +; INPUT: +; b = tile ID +; c = number of tile ID's to write +; de = amount to destination address after each tile (1 for horizontal, 20 for vertical) +; hl = destination address +DrawTileLine: + push bc + push de +.loop + ld [hl],b + add hl,de + dec c + jr nz,.loop + pop de + pop bc + ret + +INCLUDE "data/pokedex_entries.asm" + +PokedexToIndex: + ; converts the Pokédex number at wd11e to an index + push bc + push hl + ld a,[wd11e] + ld b,a + ld c,0 + ld hl,PokedexOrder + +.loop ; go through the list until we find an entry with a matching dex number + inc c + ld a,[hli] + cp b + jr nz,.loop + + ld a,c + ld [wd11e],a + pop hl + pop bc + ret + +IndexToPokedex: + ; converts the index number at wd11e to a Pokédex number + push bc + push hl + ld a,[wd11e] + dec a + ld hl,PokedexOrder + ld b,0 + ld c,a + add hl,bc + ld a,[hl] + ld [wd11e],a + pop hl + pop bc + ret + +INCLUDE "data/pokedex_order.asm" diff --git a/de/engine/menu/prize_menu.asm b/de/engine/menu/prize_menu.asm new file mode 100755 index 00000000..5cfdbc87 --- /dev/null +++ b/de/engine/menu/prize_menu.asm @@ -0,0 +1,306 @@ +CeladonPrizeMenu: + ld b,COIN_CASE + call IsItemInBag + jr nz,.havingCoinCase + ld hl,RequireCoinCaseTextPtr + jp PrintText +.havingCoinCase + ld hl,wd730 + set 6,[hl] ; disable letter-printing delay + ld hl,ExchangeCoinsForPrizesTextPtr + call PrintText +; the following are the menu settings + xor a + ld [wCurrentMenuItem],a + ld [wLastMenuItem],a + ld a,A_BUTTON | B_BUTTON + ld [wMenuWatchedKeys],a + ld a,$03 + ld [wMaxMenuItem],a + ld a,$04 + ld [wTopMenuItemY],a + ld a,$01 + ld [wTopMenuItemX],a + call PrintPrizePrice + coord hl, 0, 2 + ld b, 8 + ld c, 16 + call TextBoxBorder + call GetPrizeMenuId + call UpdateSprites + ld hl,WhichPrizeTextPtr + call PrintText + call HandleMenuInput ; menu choice handler + bit 1,a ; keypress = B (Cancel) + jr nz, .noChoice + ld a,[wCurrentMenuItem] + cp 3 ; "NO,THANKS" choice + jr z, .noChoice + call HandlePrizeChoice +.noChoice + ld hl,wd730 + res 6,[hl] + ret + +RequireCoinCaseTextPtr: + TX_FAR _RequireCoinCaseText + TX_WAIT + db "@" + +ExchangeCoinsForPrizesTextPtr: + TX_FAR _ExchangeCoinsForPrizesText + db "@" + +WhichPrizeTextPtr: + TX_FAR _WhichPrizeText + db "@" + +GetPrizeMenuId: +; determine which one among the three +; prize-texts has been selected +; using the text ID (stored in [hSpriteIndexOrTextID]) +; load the three prizes at wd13d-wd13f +; load the three prices at wd141-wd146 +; display the three prizes' names +; (distinguishing between Pokemon names +; and Items (specifically TMs) names) + ld a,[hSpriteIndexOrTextID] + sub 3 ; prize-texts' id are 3, 4 and 5 + ld [wWhichPrizeWindow],a ; prize-texts' id (relative, i.e. 0, 1 or 2) + add a + add a + ld d,0 + ld e,a + ld hl,PrizeDifferentMenuPtrs + add hl,de + ld a,[hli] + ld d,[hl] + ld e,a + inc hl + push hl + ld hl,wPrize1 + call CopyString + pop hl + ld a,[hli] + ld h,[hl] + ld l,a + ld de,wPrize1Price + ld bc,6 + call CopyData + ld a,[wWhichPrizeWindow] + cp 2 ;is TM_menu? + jr nz,.putMonName + ld a,[wPrize1] + ld [wd11e],a + call GetItemName + coord hl, 2, 4 + call PlaceString + ld a,[wPrize2] + ld [wd11e],a + call GetItemName + coord hl, 2, 6 + call PlaceString + ld a,[wPrize3] + ld [wd11e],a + call GetItemName + coord hl, 2, 8 + call PlaceString + jr .putNoThanksText +.putMonName + ld a,[wPrize1] + ld [wd11e],a + call GetMonName + coord hl, 2, 4 + call PlaceString + ld a,[wPrize2] + ld [wd11e],a + call GetMonName + coord hl, 2, 6 + call PlaceString + ld a,[wPrize3] + ld [wd11e],a + call GetMonName + coord hl, 2, 8 + call PlaceString +.putNoThanksText + coord hl, 2, 10 + ld de,NoThanksText + call PlaceString +; put prices on the right side of the textbox + ld de,wPrize1Price + coord hl, 13, 5 +; reg. c: +; [low nybble] number of bytes +; [bit 765 = %100] space-padding (not zero-padding) + ld c,(1 << 7 | 2) +; Function $15CD displays BCD value (same routine +; used by text-command $02) + call PrintBCDNumber + ld de,wPrize2Price + coord hl, 13, 7 + ld c,(1 << 7 | 2) + call PrintBCDNumber + ld de,wPrize3Price + coord hl, 13, 9 + ld c,(1 << 7 | 2) + jp PrintBCDNumber + +INCLUDE "data/prizes.asm" + +PrintPrizePrice: + coord hl, 11, 0 + ld b, 1 + ld c, 7 + call TextBoxBorder + call UpdateSprites + coord hl, 13, 0 + ld de, .CoinString + call PlaceString + coord hl, 13, 1 + ld de, .SixSpacesString + call PlaceString + coord hl, 13, 1 + ld de,wPlayerCoins + ld c,%10000010 + call PrintBCDNumber + ret + +.CoinString: + db "MÜNZEN@" + +.SixSpacesString: + db " @" + +LoadCoinsToSubtract: + ld a,[wWhichPrize] + add a + ld d,0 + ld e,a + ld hl,wPrize1Price + add hl,de ; get selected prize's price + xor a + ld [hUnusedCoinsByte],a + ld a,[hli] + ld [hCoins],a + ld a,[hl] + ld [hCoins + 1],a + ret + +HandlePrizeChoice: + ld a,[wCurrentMenuItem] + ld [wWhichPrize],a + ld d,0 + ld e,a + ld hl,wPrize1 + add hl,de + ld a,[hl] + ld [wd11e],a + ld a,[wWhichPrizeWindow] + cp 2 ; is prize a TM? + jr nz, .getMonName + call GetItemName + jr .givePrize +.getMonName + call GetMonName +.givePrize + ld hl,SoYouWantPrizeTextPtr + call PrintText + call YesNoChoice + ld a,[wCurrentMenuItem] ; yes/no answer (Y=0, N=1) + and a + jr nz, .printOhFineThen + call LoadCoinsToSubtract + call HasEnoughCoins + jr c, .notEnoughCoins + ld a,[wWhichPrizeWindow] + cp $02 + jr nz, .giveMon + ld a,[wd11e] + ld b,a + ld a,1 + ld c,a + call GiveItem + jr nc, .bagFull + jr .subtractCoins +.giveMon + ld a,[wd11e] + ld [wcf91],a + push af + call GetPrizeMonLevel + ld c,a + pop af + ld b,a + call GivePokemon + +; If either the party or box was full, wait after displaying message. + push af + ld a,[wAddedToParty] + and a + call z,WaitForTextScrollButtonPress + pop af + +; If the mon couldn't be given to the player (because both the party and box +; were full), return without subtracting coins. + ret nc + +.subtractCoins + call LoadCoinsToSubtract + ld hl,hCoins + 1 + ld de,wPlayerCoins + 1 + ld c,$02 ; how many bytes + predef SubBCDPredef + jp PrintPrizePrice +.bagFull + ld hl,PrizeRoomBagIsFullTextPtr + jp PrintText +.notEnoughCoins + ld hl,SorryNeedMoreCoinsText + jp PrintText +.printOhFineThen + ld hl,OhFineThenTextPtr + jp PrintText + +UnknownPrizeData: +; XXX what's this? + db $00,$01,$00,$01,$00,$01,$00,$00,$01 + +HereYouGoTextPtr: + TX_FAR _HereYouGoText + TX_WAIT + db "@" + +SoYouWantPrizeTextPtr: + TX_FAR _SoYouWantPrizeText + db "@" + +SorryNeedMoreCoinsText: + TX_FAR _SorryNeedMoreCoinsText + TX_WAIT + db "@" + +PrizeRoomBagIsFullTextPtr: + TX_FAR _OopsYouDontHaveEnoughRoomText + TX_WAIT + db "@" + +OhFineThenTextPtr: + TX_FAR _OhFineThenText + TX_WAIT + db "@" + +GetPrizeMonLevel: + ld a,[wcf91] + ld b,a + ld hl,PrizeMonLevelDictionary +.loop + ld a,[hli] + cp b + jr z,.matchFound + inc hl + jr .loop +.matchFound + ld a,[hl] + ld [wCurEnemyLVL],a + ret + +INCLUDE "data/prize_mon_levels.asm" diff --git a/de/engine/menu/start_sub_menus.asm b/de/engine/menu/start_sub_menus.asm new file mode 100755 index 00000000..f31c5d65 --- /dev/null +++ b/de/engine/menu/start_sub_menus.asm @@ -0,0 +1,854 @@ +StartMenu_Pokedex: + predef ShowPokedexMenu + call LoadScreenTilesFromBuffer2 ; restore saved screen + call Delay3 + call LoadGBPal + call UpdateSprites + jp RedisplayStartMenu + +StartMenu_Pokemon: + ld a,[wPartyCount] + and a + jp z,RedisplayStartMenu + xor a + ld [wMenuItemToSwap],a + ld [wPartyMenuTypeOrMessageID],a + ld [wUpdateSpritesEnabled],a + call DisplayPartyMenu + jr .checkIfPokemonChosen +.loop + xor a + ld [wMenuItemToSwap],a + ld [wPartyMenuTypeOrMessageID],a + call GoBackToPartyMenu +.checkIfPokemonChosen + jr nc,.chosePokemon +.exitMenu + call GBPalWhiteOutWithDelay3 + call RestoreScreenTilesAndReloadTilePatterns + call LoadGBPal + jp RedisplayStartMenu +.chosePokemon + call SaveScreenTilesToBuffer1 + ld a,FIELD_MOVE_MON_MENU + ld [wTextBoxID],a + call DisplayTextBoxID ; display pokemon menu options + ld hl,wFieldMoves + lb bc, 2, 12 ; max menu item ID, top menu item Y + ld e,5 +.adjustMenuVariablesLoop + dec e + jr z,.storeMenuVariables + ld a,[hli] + and a ; end of field moves? + jr z,.storeMenuVariables + inc b + dec c + dec c + jr .adjustMenuVariablesLoop +.storeMenuVariables + ld hl,wTopMenuItemY + ld a,c + ld [hli],a ; top menu item Y + ld a,[hFieldMoveMonMenuTopMenuItemX] + ld [hli],a ; top menu item X + xor a + ld [hli],a ; current menu item ID + inc hl + ld a,b + ld [hli],a ; max menu item ID + ld a,A_BUTTON | B_BUTTON + ld [hli],a ; menu watched keys + xor a + ld [hl],a + call HandleMenuInput + push af + call LoadScreenTilesFromBuffer1 ; restore saved screen + pop af + bit 1,a ; was the B button pressed? + jp nz,.loop +; if the B button wasn't pressed + ld a,[wMaxMenuItem] + ld b,a + ld a,[wCurrentMenuItem] ; menu selection + cp b + jp z,.exitMenu ; if the player chose Cancel + dec b + cp b + jr z,.choseSwitch + dec b + cp b + jp z,.choseStats + ld c,a + ld b,0 + ld hl,wFieldMoves + add hl,bc + jp .choseOutOfBattleMove +.choseSwitch + ld a,[wPartyCount] + cp a,2 ; is there more than one pokemon in the party? + jp c,StartMenu_Pokemon ; if not, no switching + call SwitchPartyMon_InitVarOrSwapData ; init [wMenuItemToSwap] + ld a,SWAP_MONS_PARTY_MENU + ld [wPartyMenuTypeOrMessageID],a + call GoBackToPartyMenu + jp .checkIfPokemonChosen +.choseStats + call ClearSprites + xor a ; PLAYER_PARTY_DATA + ld [wMonDataLocation],a + predef StatusScreen + predef StatusScreen2 + call ReloadMapData + jp StartMenu_Pokemon +.choseOutOfBattleMove + push hl + ld a,[wWhichPokemon] + ld hl,wPartyMonNicks + call GetPartyMonName + pop hl + ld a,[hl] + dec a + add a + ld b,0 + ld c,a + ld hl,.outOfBattleMovePointers + add hl,bc + ld a,[hli] + ld h,[hl] + ld l,a + ld a,[wObtainedBadges] ; badges obtained + jp hl +.outOfBattleMovePointers + dw .cut + dw .fly + dw .surf + dw .surf + dw .strength + dw .flash + dw .dig + dw .teleport + dw .softboiled +.fly + bit 2,a ; does the player have the Thunder Badge? + jp z,.newBadgeRequired + call CheckIfInOutsideMap + jr z,.canFly + ld a,[wWhichPokemon] + ld hl,wPartyMonNicks + call GetPartyMonName + ld hl,.cannotFlyHereText + call PrintText + jp .loop +.canFly + call ChooseFlyDestination + ld a,[wd732] + bit 3,a ; did the player decide to fly? + jp nz,.goBackToMap + call LoadFontTilePatterns + ld hl,wd72e + set 1,[hl] + jp StartMenu_Pokemon +.cut + bit 1,a ; does the player have the Cascade Badge? + jp z,.newBadgeRequired + predef UsedCut + ld a,[wActionResultOrTookBattleTurn] + and a + jp z,.loop + jp CloseTextDisplay +.surf + bit 4,a ; does the player have the Soul Badge? + jp z,.newBadgeRequired + callba IsSurfingAllowed + ld hl,wd728 + bit 1,[hl] + res 1,[hl] + jp z,.loop + ld a,SURFBOARD + ld [wcf91],a + ld [wPseudoItemID],a + call UseItem + ld a,[wActionResultOrTookBattleTurn] + and a + jp z,.loop + call GBPalWhiteOutWithDelay3 + jp .goBackToMap +.strength + bit 3,a ; does the player have the Rainbow Badge? + jp z,.newBadgeRequired + predef PrintStrengthTxt + call GBPalWhiteOutWithDelay3 + jp .goBackToMap +.flash + bit 0,a ; does the player have the Boulder Badge? + jp z,.newBadgeRequired + xor a + ld [wMapPalOffset],a + ld hl,.flashLightsAreaText + call PrintText + call GBPalWhiteOutWithDelay3 + jp .goBackToMap +.flashLightsAreaText + TX_FAR _FlashLightsAreaText + db "@" +.dig + ld a,ESCAPE_ROPE + ld [wcf91],a + ld [wPseudoItemID],a + call UseItem + ld a,[wActionResultOrTookBattleTurn] + and a + jp z,.loop + call GBPalWhiteOutWithDelay3 + jp .goBackToMap +.teleport + call CheckIfInOutsideMap + jr z,.canTeleport + ld a,[wWhichPokemon] + ld hl,wPartyMonNicks + call GetPartyMonName + ld hl,.cannotUseTeleportNowText + call PrintText + jp .loop +.canTeleport + ld hl,.warpToLastPokemonCenterText + call PrintText + ld hl,wd732 + set 3,[hl] + set 6,[hl] + ld hl,wd72e + set 1,[hl] + res 4,[hl] + ld c,60 + call DelayFrames + call GBPalWhiteOutWithDelay3 + jp .goBackToMap +.warpToLastPokemonCenterText + TX_FAR _WarpToLastPokemonCenterText + db "@" +.cannotUseTeleportNowText + TX_FAR _CannotUseTeleportNowText + db "@" +.cannotFlyHereText + TX_FAR _CannotFlyHereText + db "@" +.softboiled + ld hl,wPartyMon1MaxHP + ld a,[wWhichPokemon] + ld bc,wPartyMon2 - wPartyMon1 + call AddNTimes + ld a,[hli] + ld [H_DIVIDEND],a + ld a,[hl] + ld [H_DIVIDEND + 1],a + ld a,5 + ld [H_DIVISOR],a + ld b,2 ; number of bytes + call Divide + ld bc,wPartyMon1HP - wPartyMon1MaxHP + add hl,bc + ld a,[hld] + ld b,a + ld a,[H_QUOTIENT + 3] + sub b + ld b,[hl] + ld a,[H_QUOTIENT + 2] + sbc b + jp nc,.notHealthyEnough + ld a,[wPartyAndBillsPCSavedMenuItem] + push af + ld a,POTION + ld [wcf91],a + ld [wPseudoItemID],a + call UseItem + pop af + ld [wPartyAndBillsPCSavedMenuItem],a + jp .loop +.notHealthyEnough ; if current HP is less than 1/5 of max HP + ld hl,.notHealthyEnoughText + call PrintText + jp .loop +.notHealthyEnoughText + TX_FAR _NotHealthyEnoughText + db "@" +.goBackToMap + call RestoreScreenTilesAndReloadTilePatterns + jp CloseTextDisplay +.newBadgeRequired + ld hl,.newBadgeRequiredText + call PrintText + jp .loop +.newBadgeRequiredText + TX_FAR _NewBadgeRequiredText + db "@" + +; writes a blank tile to all possible menu cursor positions on the party menu +ErasePartyMenuCursors: + coord hl, 0, 1 + ld bc,2 * 20 ; menu cursor positions are 2 rows apart + ld a,6 ; 6 menu cursor positions +.loop + ld [hl]," " + add hl,bc + dec a + jr nz,.loop + ret + +ItemMenuLoop: + call LoadScreenTilesFromBuffer2DisableBGTransfer ; restore saved screen + call RunDefaultPaletteCommand + +StartMenu_Item: + ld a,[wLinkState] + dec a ; is the player in the Colosseum or Trade Centre? + jr nz,.notInCableClubRoom + ld hl,CannotUseItemsHereText + call PrintText + jr .exitMenu +.notInCableClubRoom + ld bc,wNumBagItems + ld hl,wListPointer + ld a,c + ld [hli],a + ld [hl],b ; store item bag pointer in wListPointer (for DisplayListMenuID) + 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 + jr nc,.choseItem +.exitMenu + call LoadScreenTilesFromBuffer2 ; restore saved screen + call LoadTextBoxTilePatterns + call UpdateSprites + jp RedisplayStartMenu +.choseItem +; erase menu cursor (blank each tile in front of an item name) + ld a," " + Coorda 5, 4 + Coorda 5, 6 + Coorda 5, 8 + Coorda 5, 10 + call PlaceUnfilledArrowMenuCursor + xor a + ld [wMenuItemToSwap],a + ld a,[wcf91] + cp a,BICYCLE + jp z,.useOrTossItem +.notBicycle1 + ld a,USE_TOSS_MENU_TEMPLATE + ld [wTextBoxID],a + call DisplayTextBoxID + ld hl,wTopMenuItemY + ld a,11 + ld [hli],a ; top menu item Y + ld a,14 + ld [hli],a ; top menu item X + xor a + ld [hli],a ; current menu item ID + inc hl + inc a ; a = 1 + ld [hli],a ; max menu item ID + ld a,A_BUTTON | B_BUTTON + ld [hli],a ; menu watched keys + xor a + ld [hl],a ; old menu item id + call HandleMenuInput + call PlaceUnfilledArrowMenuCursor + bit 1,a ; was the B button pressed? + jr z,.useOrTossItem + jp ItemMenuLoop +.useOrTossItem ; if the player made the choice to use or toss the item + ld a,[wcf91] + ld [wd11e],a + call GetItemName + call CopyStringToCF50 ; copy name to wcf50 + ld a,[wcf91] + cp a,BICYCLE + jr nz,.notBicycle2 + ld a,[wd732] + bit 5,a + jr z,.useItem_closeMenu + ld hl,CannotGetOffHereText + call PrintText + jp ItemMenuLoop +.notBicycle2 + ld a,[wCurrentMenuItem] + and a + jr nz,.tossItem +; use item + ld [wPseudoItemID],a ; a must be 0 due to above conditional jump + ld a,[wcf91] + cp a,HM_01 + jr nc,.useItem_partyMenu + ld hl,UsableItems_CloseMenu + ld de,1 + call IsInArray + jr c,.useItem_closeMenu + ld a,[wcf91] + ld hl,UsableItems_PartyMenu + ld de,1 + call IsInArray + jr c,.useItem_partyMenu + call UseItem + jp ItemMenuLoop +.useItem_closeMenu + xor a + ld [wPseudoItemID],a + call UseItem + ld a,[wActionResultOrTookBattleTurn] + and a + jp z,ItemMenuLoop + jp CloseStartMenu +.useItem_partyMenu + ld a,[wUpdateSpritesEnabled] + push af + call UseItem + ld a,[wActionResultOrTookBattleTurn] + cp a,$02 + jp z,.partyMenuNotDisplayed + call GBPalWhiteOutWithDelay3 + call RestoreScreenTilesAndReloadTilePatterns + pop af + ld [wUpdateSpritesEnabled],a + jp StartMenu_Item +.partyMenuNotDisplayed + pop af + ld [wUpdateSpritesEnabled],a + jp ItemMenuLoop +.tossItem + call IsKeyItem + ld a,[wIsKeyItem] + and a + jr nz,.skipAskingQuantity + ld a,[wcf91] + call IsItemHM + jr c,.skipAskingQuantity + call DisplayChooseQuantityMenu + inc a + jr z,.tossZeroItems +.skipAskingQuantity + ld hl,wNumBagItems + call TossItem +.tossZeroItems + jp ItemMenuLoop + +CannotUseItemsHereText: + TX_FAR _CannotUseItemsHereText + db "@" + +CannotGetOffHereText: + TX_FAR _CannotGetOffHereText + db "@" + +; items which bring up the party menu when used +UsableItems_PartyMenu: + db MOON_STONE + db ANTIDOTE + db BURN_HEAL + db ICE_HEAL + db AWAKENING + db PARLYZ_HEAL + db FULL_RESTORE + db MAX_POTION + db HYPER_POTION + db SUPER_POTION + db POTION + db FIRE_STONE + db THUNDER_STONE + db WATER_STONE + db HP_UP + db PROTEIN + db IRON + db CARBOS + db CALCIUM + db RARE_CANDY + db LEAF_STONE + db FULL_HEAL + db REVIVE + db MAX_REVIVE + db FRESH_WATER + db SODA_POP + db LEMONADE + db X_ATTACK + db X_DEFEND + db X_SPEED + db X_SPECIAL + db PP_UP + db ETHER + db MAX_ETHER + db ELIXER + db MAX_ELIXER + db $ff + +; items which close the item menu when used +UsableItems_CloseMenu: + db ESCAPE_ROPE + db ITEMFINDER + db POKE_FLUTE + db OLD_ROD + db GOOD_ROD + db SUPER_ROD + db $ff + +StartMenu_TrainerInfo: + call GBPalWhiteOut + call ClearScreen + call UpdateSprites + ld a,[hTilesetType] + push af + xor a + ld [hTilesetType],a + call DrawTrainerInfo + predef DrawBadges ; draw badges + ld b, SET_PAL_TRAINER_CARD + call RunPaletteCommand + call GBPalNormal + call WaitForTextScrollButtonPress ; wait for button press + call GBPalWhiteOut + call LoadFontTilePatterns + call LoadScreenTilesFromBuffer2 ; restore saved screen + call RunDefaultPaletteCommand + call ReloadMapData + call LoadGBPal + pop af + ld [hTilesetType],a + jp RedisplayStartMenu + +; loads tile patterns and draws everything except for gym leader faces / badges +DrawTrainerInfo: + ld de,RedPicFront + lb bc, BANK(RedPicFront), $01 + predef DisplayPicCenteredOrUpperRight + call DisableLCD + coord hl, 0, 2 + ld a," " + call TrainerInfo_DrawVerticalLine + coord hl, 1, 2 + call TrainerInfo_DrawVerticalLine + ld hl,vChars2 + $70 + ld de,vChars2 + ld bc,$70 * 4 + call CopyData + ld hl,TrainerInfoTextBoxTileGraphics ; trainer info text box tile patterns + ld de,vChars2 + $770 + ld bc,$0080 + push bc + call TrainerInfo_FarCopyData + ld hl,BlankLeaderNames + ld de,vChars2 + $600 + ld bc,$0170 + call TrainerInfo_FarCopyData + pop bc + ld hl,BadgeNumbersTileGraphics ; badge number tile patterns + ld de,vChars1 + $580 + call TrainerInfo_FarCopyData + ld hl,GymLeaderFaceAndBadgeTileGraphics ; gym leader face and badge tile patterns + ld de,vChars2 + $200 + ld bc,$0400 + ld a,$03 + call FarCopyData2 + ld hl,TextBoxGraphics + ld de,$00d0 + add hl,de ; hl = colon tile pattern + ld de,vChars1 + $560 + ld bc,$0010 + ld a,$04 + push bc + call FarCopyData2 + pop bc + ld hl,TrainerInfoTextBoxTileGraphics + $80 ; background tile pattern + ld de,vChars1 + $570 + call TrainerInfo_FarCopyData + call EnableLCD + ld hl,wTrainerInfoTextBoxWidthPlus1 + ld a,18 + 1 + ld [hli],a + dec a + ld [hli],a + ld [hl],1 + coord hl, 0, 0 + call TrainerInfo_DrawTextBox + ld hl,wTrainerInfoTextBoxWidthPlus1 + ld a,16 + 1 + ld [hli],a + dec a + ld [hli],a + ld [hl],3 + coord hl, 1, 10 + call TrainerInfo_DrawTextBox + coord hl, 0, 10 + ld a,$d7 + call TrainerInfo_DrawVerticalLine + coord hl, 19, 10 + call TrainerInfo_DrawVerticalLine + coord hl, 6, 9 + ld de,TrainerInfo_BadgesText + call PlaceString + coord hl, 2, 2 + ld de,TrainerInfo_NameMoneyTimeText + call PlaceString + coord hl, 7, 2 + ld de,wPlayerName + call PlaceString + coord hl, 8, 4 + ld de,wPlayerMoney + ld c,$e3 + call PrintBCDNumber + coord hl, 9, 6 + ld de,wPlayTimeHours ; hours + lb bc, LEFT_ALIGN | 1, 3 + call PrintNumber + ld [hl],$d6 ; colon tile ID + inc hl + ld de,wPlayTimeMinutes ; minutes + lb bc, LEADING_ZEROES | 1, 2 + jp PrintNumber + +TrainerInfo_FarCopyData: + ld a,BANK(TrainerInfoTextBoxTileGraphics) + jp FarCopyData2 + +TrainerInfo_NameMoneyTimeText: + db "NAME/" + next "GELD/" + next "ZEIT/@" + +; $76 is a circle tile +TrainerInfo_BadgesText: + db $76,"ORDEN",$76,"@" + +; draws a text box on the trainer info screen +; height is always 6 +; INPUT: +; hl = destination address +; [wTrainerInfoTextBoxWidthPlus1] = width +; [wTrainerInfoTextBoxWidth] = width - 1 +; [wTrainerInfoTextBoxNextRowOffset] = distance from the end of a text box row to the start of the next +TrainerInfo_DrawTextBox: + ld a,$79 ; upper left corner tile ID + lb de, $7a, $7b ; top edge and upper right corner tile ID's + call TrainerInfo_DrawHorizontalEdge ; draw top edge + call TrainerInfo_NextTextBoxRow + ld a,[wTrainerInfoTextBoxWidthPlus1] + ld e,a + ld d,0 + ld c,6 ; height of the text box +.loop + ld [hl],$7c ; left edge tile ID + add hl,de + ld [hl],$78 ; right edge tile ID + call TrainerInfo_NextTextBoxRow + dec c + jr nz,.loop + ld a,$7d ; lower left corner tile ID + lb de,$77, $7e ; bottom edge and lower right corner tile ID's + +TrainerInfo_DrawHorizontalEdge: + ld [hli],a ; place left corner tile + ld a,[wTrainerInfoTextBoxWidth] + ld c,a + ld a,d +.loop + ld [hli],a ; place edge tile + dec c + jr nz,.loop + ld a,e + ld [hl],a ; place right corner tile + ret + +TrainerInfo_NextTextBoxRow: + ld a,[wTrainerInfoTextBoxNextRowOffset] ; distance to the start of the next row +.loop + inc hl + dec a + jr nz,.loop + ret + +; draws a vertical line +; INPUT: +; hl = address of top tile in the line +; a = tile ID +TrainerInfo_DrawVerticalLine: + ld de,SCREEN_WIDTH + ld c,8 +.loop + ld [hl],a + add hl,de + dec c + jr nz,.loop + ret + +StartMenu_SaveReset: + ld a,[wd72e] + bit 6,a ; is the player using the link feature? + jp nz,Init + predef SaveSAV ; save the game + call LoadScreenTilesFromBuffer2 ; restore saved screen + jp HoldTextDisplayOpen + +StartMenu_Option: + xor a + ld [H_AUTOBGTRANSFERENABLED],a + call ClearScreen + call UpdateSprites + callab DisplayOptionMenu + call LoadScreenTilesFromBuffer2 ; restore saved screen + call LoadTextBoxTilePatterns + call UpdateSprites + jp RedisplayStartMenu + +SwitchPartyMon: + call SwitchPartyMon_InitVarOrSwapData ; swap data + ld a, [wSwappedMenuItem] + call SwitchPartyMon_ClearGfx + ld a, [wCurrentMenuItem] + call SwitchPartyMon_ClearGfx + jp RedrawPartyMenu_ + +SwitchPartyMon_ClearGfx: + push af + coord hl, 0, 0 + ld bc, SCREEN_WIDTH * 2 + call AddNTimes + ld c, SCREEN_WIDTH * 2 + ld a, " " +.clearMonBGLoop ; clear the mon's row in the party menu + ld [hli], a + dec c + jr nz, .clearMonBGLoop + pop af + ld hl, wOAMBuffer + ld bc, $10 + call AddNTimes + ld de, $4 + ld c, e +.clearMonOAMLoop + ld [hl], $a0 + add hl, de + dec c + jr nz, .clearMonOAMLoop + call WaitForSoundToFinish + ld a, SFX_SWAP + jp PlaySound + +SwitchPartyMon_InitVarOrSwapData: +; This is used to initialise [wMenuItemToSwap] and to actually swap the data. + ld a, [wMenuItemToSwap] + and a ; has [wMenuItemToSwap] been initialised yet? + jr nz, .pickedMonsToSwap +; If not, initialise [wMenuItemToSwap] so that it matches the current mon. + ld a, [wWhichPokemon] + inc a ; [wMenuItemToSwap] counts from 1 + ld [wMenuItemToSwap], a + ret +.pickedMonsToSwap + xor a + ld [wPartyMenuTypeOrMessageID], a + ld a, [wMenuItemToSwap] + dec a + ld b, a + ld a, [wCurrentMenuItem] + ld [wSwappedMenuItem], a + cp b ; swapping a mon with itself? + jr nz, .swappingDifferentMons +; can't swap a mon with itself + xor a + ld [wMenuItemToSwap], a + ld [wPartyMenuTypeOrMessageID], a + ret +.swappingDifferentMons + ld a, b + ld [wMenuItemToSwap], a + push hl + push de + ld hl, wPartySpecies + ld d, h + ld e, l + ld a, [wCurrentMenuItem] + add l + ld l, a + jr nc, .noCarry + inc h +.noCarry + ld a, [wMenuItemToSwap] + add e + ld e, a + jr nc, .noCarry2 + inc d +.noCarry2 + ld a, [hl] + ld [hSwapTemp], a + ld a, [de] + ld [hl], a + ld a, [hSwapTemp] + ld [de], a + ld hl, wPartyMons + ld bc, wPartyMon2 - wPartyMon1 + ld a, [wCurrentMenuItem] + call AddNTimes + push hl + ld de, wSwitchPartyMonTempBuffer + ld bc, wPartyMon2 - wPartyMon1 + call CopyData + ld hl, wPartyMons + ld bc, wPartyMon2 - wPartyMon1 + ld a, [wMenuItemToSwap] + call AddNTimes + pop de + push hl + ld bc, wPartyMon2 - wPartyMon1 + call CopyData + pop de + ld hl, wSwitchPartyMonTempBuffer + ld bc, wPartyMon2 - wPartyMon1 + call CopyData + ld hl, wPartyMonOT + ld a, [wCurrentMenuItem] + call SkipFixedLengthTextEntries + push hl + ld de, wSwitchPartyMonTempBuffer + ld bc, NAME_LENGTH + call CopyData + ld hl, wPartyMonOT + ld a, [wMenuItemToSwap] + call SkipFixedLengthTextEntries + pop de + push hl + ld bc, NAME_LENGTH + call CopyData + pop de + ld hl, wSwitchPartyMonTempBuffer + ld bc, NAME_LENGTH + call CopyData + ld hl, wPartyMonNicks + ld a, [wCurrentMenuItem] + call SkipFixedLengthTextEntries + push hl + ld de, wSwitchPartyMonTempBuffer + ld bc, NAME_LENGTH + call CopyData + ld hl, wPartyMonNicks + ld a, [wMenuItemToSwap] + call SkipFixedLengthTextEntries + pop de + push hl + ld bc, NAME_LENGTH + call CopyData + pop de + ld hl, wSwitchPartyMonTempBuffer + ld bc, NAME_LENGTH + call CopyData + ld a, [wMenuItemToSwap] + ld [wSwappedMenuItem], a + xor a + ld [wMenuItemToSwap], a + ld [wPartyMenuTypeOrMessageID], a + pop de + pop hl + ret diff --git a/de/engine/menu/status_screen.asm b/de/engine/menu/status_screen.asm new file mode 100755 index 00000000..2db9a908 --- /dev/null +++ b/de/engine/menu/status_screen.asm @@ -0,0 +1,491 @@ +DrawHP: +; Draws the HP bar in the stats screen + call GetPredefRegisters + ld a, $1 + jr DrawHP_ + +DrawHP2: +; Draws the HP bar in the party screen + call GetPredefRegisters + ld a, $2 + +DrawHP_: + ld [wHPBarType], a + push hl + ld a, [wLoadedMonHP] + ld b, a + ld a, [wLoadedMonHP + 1] + ld c, a + or b + jr nz, .nonzeroHP + xor a + ld c, a + ld e, a + ld a, $6 + ld d, a + jp .drawHPBarAndPrintFraction +.nonzeroHP + ld a, [wLoadedMonMaxHP] + ld d, a + ld a, [wLoadedMonMaxHP + 1] + ld e, a + predef HPBarLength + ld a, $6 + ld d, a + ld c, a +.drawHPBarAndPrintFraction + pop hl + push de + push hl + push hl + call DrawHPBar + pop hl + ld a, [hFlags_0xFFF6] + bit 0, a + jr z, .printFractionBelowBar + ld bc, $9 ; right of bar + jr .printFraction +.printFractionBelowBar + ld bc, SCREEN_WIDTH + 1 ; below bar +.printFraction + add hl, bc + ld de, wLoadedMonHP + lb bc, 2, 3 + call PrintNumber + ld a, "/" + ld [hli], a + ld de, wLoadedMonMaxHP + lb bc, 2, 3 + call PrintNumber + pop hl + pop de + ret + + +; Predef 0x37 +StatusScreen: + call LoadMonData + ld a, [wMonDataLocation] + cp BOX_DATA + jr c, .DontRecalculate +; mon is in a box or daycare + ld a, [wLoadedMonBoxLevel] + ld [wLoadedMonLevel], a + ld [wCurEnemyLVL], a + ld hl, wLoadedMonHPExp - 1 + ld de, wLoadedMonStats + ld b, $1 + call CalcStats ; Recalculate stats +.DontRecalculate + ld hl, wd72c + set 1, [hl] + ld a, $33 + ld [rNR50], a ; Reduce the volume + call GBPalWhiteOutWithDelay3 + call ClearScreen + call UpdateSprites + call LoadHpBarAndStatusTilePatterns + ld de, BattleHudTiles1 ; source + ld hl, vChars2 + $6d0 ; dest + lb bc, BANK(BattleHudTiles1), $03 + call CopyVideoDataDouble ; ·│ :L and halfarrow line end + ld de, BattleHudTiles2 + ld hl, vChars2 + $780 + lb bc, BANK(BattleHudTiles2), $01 + call CopyVideoDataDouble ; │ + ld de, BattleHudTiles3 + ld hl, vChars2 + $760 + lb bc, BANK(BattleHudTiles3), $02 + call CopyVideoDataDouble ; ─┘ + ld de, PTile + ld hl, vChars2 + $720 + lb bc, BANK(PTile), (PTileEnd - PTile) / $8 + call CopyVideoDataDouble ; P (for PP), inline + ld a, [hTilesetType] + push af + xor a + ld [hTilesetType], a + coord hl, 19, 1 + lb bc, 6, 10 + call DrawLineBox ; Draws the box around name, HP and status + ld de, -6 + add hl, de + ld [hl], "⠄" ; . after No ("." is a different one) + dec hl + ld [hl], "№" + coord hl, 19, 9 + lb bc, 8, 6 + call DrawLineBox ; Draws the box around types, ID No. and OT + coord hl, 10, 9 + ld de, Type1Text + call PlaceString ; "TYPE1/" + coord hl, 11, 3 + predef DrawHP + ld hl, wStatusScreenHPBarColor + call GetHealthBarColor + ld b, SET_PAL_STATUS_SCREEN + call RunPaletteCommand + coord hl, 16, 6 + ld de, wLoadedMonStatus + call PrintStatusCondition + jr nz, .StatusWritten + coord hl, 16, 6 + ld de, OKText + call PlaceString ; "OK" +.StatusWritten + coord hl, 9, 6 + ld de, StatusText + call PlaceString ; "STATUS/" + coord hl, 14, 2 + call PrintLevel ; Pokémon level + ld a, [wMonHIndex] + ld [wd11e], a + ld [wd0b5], a + predef IndexToPokedex + coord hl, 3, 7 + ld de, wd11e + lb bc, LEADING_ZEROES | 1, 3 + call PrintNumber ; Pokémon no. + coord hl, 11, 10 + predef PrintMonType + ld hl, NamePointers2 + call .GetStringPointer + ld d, h + ld e, l + coord hl, 9, 1 + call PlaceString ; Pokémon name + ld hl, OTPointers + call .GetStringPointer + ld d, h + ld e, l + coord hl, 12, 16 + call PlaceString ; OT + coord hl, 12, 14 + ld de, wLoadedMonOTID + lb bc, LEADING_ZEROES | 2, 5 + call PrintNumber ; ID Number + ld d, $0 + call PrintStatsBox + call Delay3 + call GBPalNormal + coord hl, 1, 0 + call LoadFlippedFrontSpriteByMonIndex ; draw Pokémon picture + ld a, [wcf91] + call PlayCry ; play Pokémon cry + call WaitForTextScrollButtonPress ; wait for button + pop af + ld [hTilesetType], a + ret + +.GetStringPointer + ld a, [wMonDataLocation] + add a + ld c, a + ld b, 0 + add hl, bc + ld a, [hli] + ld h, [hl] + ld l, a + ld a, [wMonDataLocation] + cp DAYCARE_DATA + ret z + ld a, [wWhichPokemon] + jp SkipFixedLengthTextEntries + +OTPointers: + dw wPartyMonOT + dw wEnemyMonOT + dw wBoxMonOT + dw wDayCareMonOT + +NamePointers2: + dw wPartyMonNicks + dw wEnemyMonNicks + dw wBoxMonNicks + dw wDayCareMonName + +Type1Text: + db "TYP1/", $4e + +Type2Text: + db "TYP2/", $4e + +IDNoText: + db "″№/", $4e + +OTText: + db "OT/" + next "@" + +StatusText: + db "STATUS/@" + +OKText: + db "OK@" + +; Draws a line starting from hl high b and wide c +DrawLineBox: + ld de, SCREEN_WIDTH ; New line +.PrintVerticalLine + ld [hl], $78 ; │ + add hl, de + dec b + jr nz, .PrintVerticalLine + ld [hl], $77 ; ┘ + dec hl +.PrintHorizLine + ld [hl], $76 ; ─ + dec hl + dec c + jr nz, .PrintHorizLine + ld [hl], $6f ; ← (halfarrow ending) + ret + +PTile: ; This is a single 1bpp "P" tile + INCBIN "gfx/p_tile.1bpp" +PTileEnd: + +PrintStatsBox: + ld a, d + and a ; a is 0 from the status screen + jr nz, .DifferentBox + coord hl, 0, 8 + ld b, 8 + ld c, 8 + call TextBoxBorder ; Draws the box + coord hl, 1, 9 ; Start printing stats from here + ld bc, $0019 ; Number offset + jr .PrintStats +.DifferentBox + coord hl, 9, 2 + ld b, 8 + ld c, 9 + call TextBoxBorder + coord hl, 11, 3 + ld bc, $0018 +.PrintStats + push bc + push hl + ld de, StatsText + call PlaceString + pop hl + pop bc + add hl, bc + ld de, wLoadedMonAttack + lb bc, 2, 3 + call PrintStat + ld de, wLoadedMonDefense + call PrintStat + ld de, wLoadedMonSpeed + call PrintStat + ld de, wLoadedMonSpecial + jp PrintNumber +PrintStat: + push hl + call PrintNumber + pop hl + ld de, SCREEN_WIDTH * 2 + add hl, de + ret + +StatsText: + db "ANGR" + next "VERT" + next "INIT" + next "SPEZ@" + +StatusScreen2: + ld a, [hTilesetType] + push af + xor a + ld [hTilesetType], a + ld [H_AUTOBGTRANSFERENABLED], a + ld bc, NUM_MOVES + 1 + ld hl, wMoves + call FillMemory + ld hl, wLoadedMonMoves + ld de, wMoves + ld bc, NUM_MOVES + call CopyData + callab FormatMovesString + coord hl, 9, 2 + lb bc, 5, 10 + call ClearScreenArea ; Clear under name + coord hl, 19, 3 + ld [hl], $78 + coord hl, 0, 8 + ld b, 8 + ld c, 18 + call TextBoxBorder ; Draw move container + coord hl, 2, 9 + ld de, wMovesString + call PlaceString ; Print moves + ld a, [wNumMovesMinusOne] + inc a + ld c, a + ld a, $4 + sub c + ld b, a ; Number of moves ? + coord hl, 11, 10 + ld de, SCREEN_WIDTH * 2 + ld a, $80 ; special P tile id + call StatusScreen_PrintAP ; Print "AP" + ld a, b + and a + jr z, .InitPP + ld c, a + ld a, "-" + call StatusScreen_PrintPP ; Fill the rest with -- +.InitPP + ld hl, wLoadedMonMoves + coord de, 14, 10 + ld b, 0 +.PrintPP + ld a, [hli] + and a + jr z, .PPDone + push bc + push hl + push de + ld hl, wCurrentMenuItem + ld a, [hl] + push af + ld a, b + ld [hl], a + push hl + callab GetMaxPP + pop hl + pop af + ld [hl], a + pop de + pop hl + push hl + ld bc, wPartyMon1PP - wPartyMon1Moves - 1 + add hl, bc + ld a, [hl] + and $3f + ld [wStatusScreenCurrentPP], a + ld h, d + ld l, e + push hl + ld de, wStatusScreenCurrentPP + lb bc, 1, 2 + call PrintNumber + ld a, "/" + ld [hli], a + ld de, wMaxPP + lb bc, 1, 2 + call PrintNumber + pop hl + ld de, SCREEN_WIDTH * 2 + add hl, de + ld d, h + ld e, l + pop hl + pop bc + inc b + ld a, b + cp $4 + jr nz, .PrintPP +.PPDone + coord hl, 9, 3 + ld de, StatusScreenExpText + call PlaceString + ld a, [wLoadedMonLevel] + push af + cp MAX_LEVEL + jr z, .Level100 + inc a + ld [wLoadedMonLevel], a ; Increase temporarily if not 100 +.Level100 + coord hl, 14, 6 + ld [hl], $70 ; 1-tile "to" + inc hl + inc hl + call PrintLevel + pop af + ld [wLoadedMonLevel], a + ld de, wLoadedMonExp + coord hl, 12, 4 + lb bc, 3, 7 + call PrintNumber ; exp + call CalcExpToLevelUp + ld de, wLoadedMonExp + coord hl, 7, 6 + lb bc, 3, 7 + call PrintNumber ; exp needed to level up + coord hl, 9, 0 + call StatusScreen_ClearName + coord hl, 9, 1 + call StatusScreen_ClearName + ld a, [wMonHIndex] + ld [wd11e], a + call GetMonName + coord hl, 9, 1 + call PlaceString + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + call Delay3 + call WaitForTextScrollButtonPress ; wait for button + pop af + ld [hTilesetType], a + ld hl, wd72c + res 1, [hl] + ld a, $77 + ld [rNR50], a + call GBPalWhiteOut + jp ClearScreen + +CalcExpToLevelUp: + ld a, [wLoadedMonLevel] + cp MAX_LEVEL + jr z, .atMaxLevel + inc a + ld d, a + callab CalcExperience + ld hl, wLoadedMonExp + 2 + ld a, [hExperience + 2] + sub [hl] + ld [hld], a + ld a, [hExperience + 1] + sbc [hl] + ld [hld], a + ld a, [hExperience] + sbc [hl] + ld [hld], a + ret +.atMaxLevel + ld hl, wLoadedMonExp + xor a + ld [hli], a + ld [hli], a + ld [hl], a + ret + +StatusScreenExpText: + db "EP-PUNKTE" + next "LEVEL UP@" + +StatusScreen_ClearName: + ld bc, 10 + ld a, " " + jp FillMemory + +StatusScreen_PrintPP: +; print PP or -- c times, going down two rows each time + ld [hli], a + ld [hld], a + add hl, de + dec c + jr nz, StatusScreen_PrintPP + ret + +StatusScreen_PrintAP: ; 12cd5 (4:6cd5) + ld a, "A" + ld [hli],a + ld a, "P" + ldd [hl], a + add hl, de + dec c + jr nz, StatusScreen_PrintAP + ret
\ No newline at end of file diff --git a/de/engine/menu/text_box.asm b/de/engine/menu/text_box.asm new file mode 100644 index 00000000..57f0aa29 --- /dev/null +++ b/de/engine/menu/text_box.asm @@ -0,0 +1,740 @@ +; function to draw various text boxes +DisplayTextBoxID_: + ld a,[wTextBoxID] + cp a,TWO_OPTION_MENU + jp z,DisplayTwoOptionMenu + ld c,a + ld hl,TextBoxFunctionTable + ld de,3 + call SearchTextBoxTable + jr c,.functionTableMatch + ld hl,TextBoxCoordTable + ld de,5 + call SearchTextBoxTable + jr c,.coordTableMatch + ld hl,TextBoxTextAndCoordTable + ld de,9 + call SearchTextBoxTable + jr c,.textAndCoordTableMatch +.done + ret +.functionTableMatch + ld a,[hli] + ld h,[hl] + ld l,a ; hl = address of function + ld de,.done + push de + jp hl ; jump to the function +.coordTableMatch + call GetTextBoxIDCoords + call GetAddressOfScreenCoords + call TextBoxBorder + ret +.textAndCoordTableMatch + call GetTextBoxIDCoords + push hl + call GetAddressOfScreenCoords + call TextBoxBorder + pop hl + call GetTextBoxIDText + ld a,[wd730] + push af + ld a,[wd730] + set 6,a ; no pauses between printing each letter + ld [wd730],a + call PlaceString + pop af + ld [wd730],a + call UpdateSprites + ret + +; function to search a table terminated with $ff for a byte matching c in increments of de +; sets carry flag if a match is found and clears carry flag if not +SearchTextBoxTable: + dec de +.loop + ld a,[hli] + cp a,$ff + jr z,.notFound + cp c + jr z,.found + add hl,de + jr .loop +.found + scf +.notFound + ret + +; function to load coordinates from the TextBoxCoordTable or the TextBoxTextAndCoordTable +; INPUT: +; hl = address of coordinates +; OUTPUT: +; b = height +; c = width +; d = row of upper left corner +; e = column of upper left corner +GetTextBoxIDCoords: + ld a,[hli] ; column of upper left corner + ld e,a + ld a,[hli] ; row of upper left corner + ld d,a + ld a,[hli] ; column of lower right corner + sub e + dec a + ld c,a ; c = width + ld a,[hli] ; row of lower right corner + sub d + dec a + ld b,a ; b = height + ret + +; function to load a text address and text coordinates from the TextBoxTextAndCoordTable +GetTextBoxIDText: + ld a,[hli] + ld e,a + ld a,[hli] + ld d,a ; de = address of text + push de ; save text address + ld a,[hli] + ld e,a ; column of upper left corner of text + ld a,[hl] + ld d,a ; row of upper left corner of text + call GetAddressOfScreenCoords + pop de ; restore text address + ret + +; function to point hl to the screen coordinates +; INPUT: +; d = row +; e = column +; OUTPUT: +; hl = address of upper left corner of text box +GetAddressOfScreenCoords: + push bc + coord hl, 0, 0 + ld bc,20 +.loop ; loop to add d rows to the base address + ld a,d + and a + jr z,.addedRows + add hl,bc + dec d + jr .loop +.addedRows + pop bc + add hl,de + ret + +; Format: +; 00: text box ID +; 01-02: function address +TextBoxFunctionTable: + dbw MONEY_BOX, DisplayMoneyBox + dbw BUY_SELL_QUIT_MENU, DoBuySellQuitMenu + dbw FIELD_MOVE_MON_MENU, DisplayFieldMoveMonMenu + db $ff ; terminator + +; Format: +; 00: text box ID +; 01: column of upper left corner +; 02: row of upper left corner +; 03: column of lower right corner +; 04: row of lower right corner +TextBoxCoordTable: + db MESSAGE_BOX, 0, 12, 19, 17 + db $03, 0, 0, 19, 14 + db $07, 0, 0, 11, 6 + db LIST_MENU_BOX, 4, 2, 19, 12 + db $10, 7, 0, 19, 17 + db MON_SPRITE_POPUP, 6, 4, 14, 13 + db $ff ; terminator + +; Format: +; 00: text box ID +; 01: column of upper left corner +; 02: row of upper left corner +; 03: column of lower right corner +; 04: row of lower right corner +; 05-06: address of text +; 07: column of beginning of text +; 08: row of beginning of text +; table of window positions and corresponding text [key, start column, start row, end column, end row, text pointer [2 bytes], text column, text row] +TextBoxTextAndCoordTable: + db JP_MOCHIMONO_MENU_TEMPLATE + db 0,0,14,17 ; text box coordinates + dw BuySellQuitText ; JapaneseMochimonoText + db 3,0 ; text coordinates + + db USE_TOSS_MENU_TEMPLATE + db 13,10,19,14 ; text box coordinates + dw UseTossText + db 15,11 ; text coordinates + + db JP_SAVE_MESSAGE_MENU_TEMPLATE + db 0,0,7,5 ; text box coordinates + dw BuySellQuitText ; JapaneseSaveMessageText + db 2,2 ; text coordinates + + db JP_SPEED_OPTIONS_MENU_TEMPLATE + db 0,6,5,10 ; text box coordinates + dw BuySellQuitText ; JapaneseSpeedOptionsText + db 2,7 ; text coordinates + + db BATTLE_MENU_TEMPLATE + db 6,12,19,17 ; text box coordinates + dw BattleMenuText + db 8,14 ; text coordinates + + db SAFARI_BATTLE_MENU_TEMPLATE + db 0,12,19,17 ; text box coordinates + dw SafariZoneBattleMenuText + db 2,14 ; text coordinates + + db SWITCH_STATS_CANCEL_MENU_TEMPLATE + db 11,11,19,17 ; text box coordinates + dw SwitchStatsCancelText + db 13,12 ; text coordinates + + db BUY_SELL_QUIT_MENU_TEMPLATE + db 0,0,10,6 ; text box coordinates + dw BuySellQuitText + 1 + db 2,1 ; text coordinates + + db MONEY_BOX_TEMPLATE + db 11,0,19,2 ; text box coordinates + dw MoneyText + db 13,0 ; text coordinates + + db JP_AH_MENU_TEMPLATE + db 7,6,11,10 ; text box coordinates + dw BuySellQuitText ; JapaneseAhText + db 8,8 ; text coordinates + + db JP_POKEDEX_MENU_TEMPLATE + db 11,8,19,17 ; text box coordinates + dw BuySellQuitText ; JapanesePokedexMenu + db 12,10 ; text coordinates + +; note that there is no terminator + +BuySellQuitText: + db "@KAUF" + next "VERKAUF" + next "TSCHÜSS!@" + +UseTossText: + db "OK" + next "MÜLL@" + +MoneyText: + db "GELD@" + +BattleMenuText: + db "KMPF ",$E1,$E2 + next "ITEM FLUCHT@" + +SafariZoneBattleMenuText: + db "BALL× KÖDER" + next "STEIN FLUCHT@" + +SwitchStatsCancelText: + db "TAUSCH" + next "STATUS" + next "ZURÜCK@" + +DisplayMoneyBox: + ld hl, wd730 + set 6, [hl] + ld a, MONEY_BOX_TEMPLATE + ld [wTextBoxID], a + call DisplayTextBoxID + coord hl, 13, 1 + ld b, 1 + ld c, 6 + call ClearScreenArea + coord hl, 12, 1 + ld de, wPlayerMoney + ld c, "d" + call PrintBCDNumber + ld hl, wd730 + res 6, [hl] + ret + +DoBuySellQuitMenu: + ld a, [wd730] + set 6, a ; no printing delay + ld [wd730], a + xor a + ld [wChosenMenuItem], a + ld a, BUY_SELL_QUIT_MENU_TEMPLATE + ld [wTextBoxID], a + call DisplayTextBoxID + ld a, A_BUTTON | B_BUTTON + ld [wMenuWatchedKeys], a + ld a, $2 + ld [wMaxMenuItem], a + ld a, $1 + ld [wTopMenuItemY], a + ld a, $1 + ld [wTopMenuItemX], a + xor a + ld [wCurrentMenuItem], a + ld [wLastMenuItem], a + ld [wMenuWatchMovingOutOfBounds], a + ld a, [wd730] + res 6, a ; turn on the printing delay + ld [wd730], a + call HandleMenuInput + call PlaceUnfilledArrowMenuCursor + bit 0, a ; was A pressed? + jr nz, .pressedA + bit 1, a ; was B pressed? (always true since only A/B are watched) + jr z, .pressedA + ld a, CANCELLED_MENU + ld [wMenuExitMethod], a + jr .quit +.pressedA + ld a, CHOSE_MENU_ITEM + ld [wMenuExitMethod], a + ld a, [wCurrentMenuItem] + ld [wChosenMenuItem], a + ld b, a + ld a, [wMaxMenuItem] + cp b + jr z, .quit + ret +.quit + ld a, CANCELLED_MENU + ld [wMenuExitMethod], a + ld a, [wCurrentMenuItem] + ld [wChosenMenuItem], a + scf + ret + +; displays a menu with two options to choose from +; b = Y of upper left corner of text region +; c = X of upper left corner of text region +; hl = address where the text box border should be drawn +DisplayTwoOptionMenu: + push hl + ld a, [wd730] + set 6, a ; no printing delay + ld [wd730], a + +; pointless because both values are overwritten before they are read + xor a + ld [wChosenMenuItem], a + ld [wMenuExitMethod], a + + ld a, A_BUTTON | B_BUTTON + ld [wMenuWatchedKeys], a + ld a, $1 + ld [wMaxMenuItem], a + ld a, b + ld [wTopMenuItemY], a + ld a, c + ld [wTopMenuItemX], a + xor a + ld [wLastMenuItem], a + ld [wMenuWatchMovingOutOfBounds], a + push hl + ld hl, wTwoOptionMenuID + bit 7, [hl] ; select second menu item by default? + res 7, [hl] + jr z, .storeCurrentMenuItem + inc a +.storeCurrentMenuItem + ld [wCurrentMenuItem], a + pop hl + push hl + push hl + call TwoOptionMenu_SaveScreenTiles + ld a, [wTwoOptionMenuID] + ld hl, TwoOptionMenuStrings + ld e, a + ld d, $0 + ld a, $5 +.menuStringLoop + add hl, de + dec a + jr nz, .menuStringLoop + ld a, [hli] + ld c, a + ld a, [hli] + ld b, a + ld e, l + ld d, h + pop hl + push de + ld a, [wTwoOptionMenuID] + cp TRADE_CANCEL_MENU + jr nz, .notTradeCancelMenu + call CableClub_TextBoxBorder + jr .afterTextBoxBorder +.notTradeCancelMenu + call TextBoxBorder +.afterTextBoxBorder + call UpdateSprites + pop hl + ld a, [hli] + and a ; put blank line before first menu item? + ld bc, 20 + 2 + jr z, .noBlankLine + ld bc, 2 * 20 + 2 +.noBlankLine + ld a, [hli] + ld e, a + ld a, [hli] + ld d, a + pop hl + add hl, bc + call PlaceString + ld hl, wd730 + res 6, [hl] ; turn on the printing delay + ld a, [wTwoOptionMenuID] + cp NO_YES_MENU + jr nz, .notNoYesMenu +; No/Yes menu +; this menu type ignores the B button +; it only seems to be used when confirming the deletion of a save file + xor a + ld [wTwoOptionMenuID], a + ld a, [wFlags_0xcd60] + push af + push hl + ld hl, wFlags_0xcd60 + bit 5, [hl] + set 5, [hl] ; don't play sound when A or B is pressed in menu + pop hl +.noYesMenuInputLoop + call HandleMenuInput + bit 1, a ; A button pressed? + jr nz, .noYesMenuInputLoop ; try again if A was not pressed + pop af + pop hl + ld [wFlags_0xcd60], a + ld a, SFX_PRESS_AB + call PlaySound + jr .pressedAButton +.notNoYesMenu + xor a + ld [wTwoOptionMenuID], a + call HandleMenuInput + pop hl + bit 1, a ; A button pressed? + jr nz, .choseSecondMenuItem ; automatically choose the second option if B is pressed +.pressedAButton + ld a, [wCurrentMenuItem] + ld [wChosenMenuItem], a + and a + jr nz, .choseSecondMenuItem +; chose first menu item + ld a, CHOSE_FIRST_ITEM + ld [wMenuExitMethod], a + ld c, 15 + call DelayFrames + call TwoOptionMenu_RestoreScreenTiles + and a + ret +.choseSecondMenuItem + ld a, 1 + ld [wCurrentMenuItem], a + ld [wChosenMenuItem], a + ld a, CHOSE_SECOND_ITEM + ld [wMenuExitMethod], a + ld c, 15 + call DelayFrames + call TwoOptionMenu_RestoreScreenTiles + scf + ret + +; Some of the wider/taller two option menus will not have the screen areas +; they cover be fully saved/restored by the two functions below. +; The bottom and right edges of the menu may remain after the function returns. + +TwoOptionMenu_SaveScreenTiles: + ld de, wBuffer + lb bc, 5, 7 +.loop + ld a, [hli] + ld [de], a + inc de + dec c + jr nz, .loop + push bc + ld bc, SCREEN_WIDTH - 7 + add hl, bc + pop bc + ld c, $7 + dec b + jr nz, .loop + ret + +TwoOptionMenu_RestoreScreenTiles: + ld de, wBuffer + lb bc, 5, 7 +.loop + ld a, [de] + inc de + ld [hli], a + dec c + jr nz, .loop + push bc + ld bc, SCREEN_WIDTH - 7 + add hl, bc + pop bc + ld c, 7 + dec b + jr nz, .loop + call UpdateSprites + ret + +; Format: +; 00: byte width +; 01: byte height +; 02: byte put blank line before first menu item +; 03: word text pointer +TwoOptionMenuStrings: + db 5,3,0 + dw .YesNoMenu + db 6,3,0 + dw .NorthWestMenu + db 6,3,0 + dw .SouthEastMenu + db 6,3,0 + dw .YesNoMenu + db 6,3,0 + dw .NorthEastMenu + db 7,3,0 + dw .TradeCancelMenu + db 7,4,1 + dw .HealCancelMenu + db 5,3,0 + dw .NoYesMenu + +.NorthWestMenu + db "NORTH" + next "WEST@" +.SouthEastMenu + db "SOUTH" + next "EAST@" +.NorthEastMenu + db "NORTH" + next "EAST@" +.NoYesMenu + db "NEIN" + next "JA@" +.YesNoMenu + db "JA" + next "NEIN@" +.TradeCancelMenu + db "TAUSCH" + next "ZURÜCK@" +.HealCancelMenu + db "HEILEN" + next "ZURÜCK@" + +DisplayFieldMoveMonMenu: + xor a + ld hl, wFieldMoves + ld [hli], a ; wFieldMoves + ld [hli], a ; wFieldMoves + 1 + ld [hli], a ; wFieldMoves + 2 + ld [hli], a ; wFieldMoves + 3 + ld [hli], a ; wNumFieldMoves + ld [hl], 12 ; wFieldMovesLeftmostXCoord + call GetMonFieldMoves + ld a, [wNumFieldMoves] + and a + jr nz, .fieldMovesExist + +; no field moves + coord hl, 11, 11 + ld b, 5 + ld c, 7 + call TextBoxBorder + call UpdateSprites + ld a, 12 + ld [hFieldMoveMonMenuTopMenuItemX], a + coord hl, 13, 12 + ld de, PokemonMenuEntries + jp PlaceString + +.fieldMovesExist + push af + +; Calculate the text box position and dimensions based on the leftmost X coord +; of the field move names before adjusting for the number of field moves. + coord hl, 0, 11 + ld a, [wFieldMovesLeftmostXCoord] + dec a + ld e, a + ld d, 0 + add hl, de + ld b, 5 + ld a, 18 + sub e + ld c, a + pop af + +; For each field move, move the top of the text box up 2 rows while the leaving +; the bottom of the text box at the bottom of the screen. + ld de, -SCREEN_WIDTH * 2 +.textBoxHeightLoop + add hl, de + inc b + inc b + dec a + jr nz, .textBoxHeightLoop + +; Make space for an extra blank row above the top field move. + ld de, -SCREEN_WIDTH + add hl, de + inc b + + call TextBoxBorder + call UpdateSprites + +; Calculate the position of the first field move name to print. + coord hl, 0, 12 + ld a, [wFieldMovesLeftmostXCoord] + inc a + ld e, a + ld d, 0 + add hl, de + ld de, -SCREEN_WIDTH * 2 + ld a, [wNumFieldMoves] +.calcFirstFieldMoveYLoop + add hl, de + dec a + jr nz, .calcFirstFieldMoveYLoop + + xor a + ld [wNumFieldMoves], a + ld de, wFieldMoves +.printNamesLoop + push hl + ld hl, FieldMoveNames + ld a, [de] + and a + jr z, .donePrintingNames + inc de + ld b, a ; index of name +.skipNamesLoop ; skip past names before the name we want + dec b + jr z, .reachedName +.skipNameLoop ; skip past current name + ld a, [hli] + cp "@" + jr nz, .skipNameLoop + jr .skipNamesLoop +.reachedName + ld b, h + ld c, l + pop hl + push de + ld d, b + ld e, c + call PlaceString + ld bc, SCREEN_WIDTH * 2 + add hl, bc + pop de + jr .printNamesLoop + +.donePrintingNames + pop hl + ld a, [wFieldMovesLeftmostXCoord] + ld [hFieldMoveMonMenuTopMenuItemX], a + coord hl, 0, 12 + ld a, [wFieldMovesLeftmostXCoord] + inc a + ld e, a + ld d, 0 + add hl, de + ld de, PokemonMenuEntries + jp PlaceString + +FieldMoveNames: + db "ZERSCHNEIDER@" + db "FLIEGEN@" + db "@" + db "SURFER@" + db "STÄRKE@" + db "BLITZ@" + db "SCHAUFLER@" + db "TELEPORT@" + db "WEICHEI@" + +PokemonMenuEntries: + db "STATUS" + next "TAUSCH" + next "ZURÜCK@" + +GetMonFieldMoves: + ld a, [wWhichPokemon] + ld hl, wPartyMon1Moves + ld bc, wPartyMon2 - wPartyMon1 + call AddNTimes + ld d, h + ld e, l + ld c, NUM_MOVES + 1 + ld hl, wFieldMoves +.loop + push hl +.nextMove + dec c + jr z, .done + ld a, [de] ; move ID + and a + jr z, .done + ld b, a + inc de + ld hl, FieldMoveDisplayData +.fieldMoveLoop + ld a, [hli] + cp $ff + jr z, .nextMove ; if the move is not a field move + cp b + jr z, .foundFieldMove + inc hl + inc hl + jr .fieldMoveLoop +.foundFieldMove + ld a, b + ld [wLastFieldMoveID], a + ld a, [hli] ; field move name index + ld b, [hl] ; field move leftmost X coordinate + pop hl + ld [hli], a ; store name index in wFieldMoves + ld a, [wNumFieldMoves] + inc a + ld [wNumFieldMoves], a + ld a, [wFieldMovesLeftmostXCoord] + cp b + jr c, .skipUpdatingLeftmostXCoord + ld a, b + ld [wFieldMovesLeftmostXCoord], a +.skipUpdatingLeftmostXCoord + ld a, [wLastFieldMoveID] + ld b, a + jr .loop +.done + pop hl + ret + +; Format: [Move id], [name index], [leftmost tile] +; Move id = id of move +; Name index = index of name in FieldMoveNames +; Leftmost tile = -1 + tile column in which the first letter of the move's name should be displayed +; "SOFTBOILED" is $08 because it has 4 more letters than "SURF", for example, whose value is $0C +FieldMoveDisplayData: + db CUT, $01, $06 + db FLY, $02, $0B + db $B4, $03, $0C ; unused field move + db SURF, $04, $0C + db STRENGTH, $05, $0C + db FLASH, $06, $0C + db DIG, $07, $09 + db TELEPORT, $08, $0A + db SOFTBOILED, $09, $0B + db $ff ; list terminator diff --git a/de/engine/menu/vending_machine.asm b/de/engine/menu/vending_machine.asm new file mode 100755 index 00000000..08f44694 --- /dev/null +++ b/de/engine/menu/vending_machine.asm @@ -0,0 +1,139 @@ +VendingMachineMenu: + ld hl, VendingMachineText1 + call PrintText + ld a, MONEY_BOX + ld [wTextBoxID], a + call DisplayTextBoxID + xor a + ld [wCurrentMenuItem], a + ld [wLastMenuItem], a + ld a, A_BUTTON | B_BUTTON + ld [wMenuWatchedKeys], a + ld a, 3 + ld [wMaxMenuItem], a + ld a, 5 + ld [wTopMenuItemY], a + ld a, 1 + ld [wTopMenuItemX], a + ld hl, wd730 + set 6, [hl] + coord hl, 0, 3 + ld b, 8 + ld c, 12 + call TextBoxBorder + call UpdateSprites + coord hl, 2, 5 + ld de, DrinkText + call PlaceString + coord hl, 9, 6 + ld de, DrinkPriceText + call PlaceString + ld hl, wd730 + res 6, [hl] + call HandleMenuInput + bit 1, a ; pressed B? + jr nz, .notThirsty + ld a, [wCurrentMenuItem] + cp 3 ; chose Cancel? + jr z, .notThirsty + xor a + ld [hMoney], a + ld [hMoney + 2], a + ld a, $2 + ld [hMoney + 1], a + call HasEnoughMoney + jr nc, .enoughMoney + ld hl, VendingMachineText4 + jp PrintText +.enoughMoney + call LoadVendingMachineItem + ld a, [hVendingMachineItem] + ld b, a + ld c, 1 + call GiveItem + jr nc, .BagFull + + ld b, 60 ; number of times to play the "brrrrr" sound +.playDeliverySound + ld c, 2 + call DelayFrames + push bc + ld a, SFX_PUSH_BOULDER + call PlaySound + pop bc + dec b + jr nz, .playDeliverySound + + ld hl, VendingMachineText5 + call PrintText + ld hl, hVendingMachinePrice + 2 + ld de, wPlayerMoney + 2 + ld c, $3 + predef SubBCDPredef + ld a, MONEY_BOX + ld [wTextBoxID], a + jp DisplayTextBoxID +.BagFull + ld hl, VendingMachineText6 + jp PrintText +.notThirsty + ld hl, VendingMachineText7 + jp PrintText + +VendingMachineText1: + TX_FAR _VendingMachineText1 + db "@" + +DrinkText: + db "TAFELWASSER" + next "SPRUDEL" + next "LIMONADE" + next "ZURÜCK@" + +DrinkPriceText: + db "¥200" + next "¥300" + next "¥350" + next "@" + +VendingMachineText4: + TX_FAR _VendingMachineText4 + db "@" + +VendingMachineText5: + TX_FAR _VendingMachineText5 + db "@" + +VendingMachineText6: + TX_FAR _VendingMachineText6 + db "@" + +VendingMachineText7: + TX_FAR _VendingMachineText7 + db "@" + +LoadVendingMachineItem: + ld hl, VendingPrices + ld a, [wCurrentMenuItem] + add a + add a + ld d, 0 + ld e, a + add hl, de + ld a, [hli] + ld [hVendingMachineItem], a + ld a, [hli] + ld [hVendingMachinePrice], a + ld a, [hli] + ld [hVendingMachinePrice + 1], a + ld a, [hl] + ld [hVendingMachinePrice + 2], a + ret + +VendingPrices: + db FRESH_WATER + money 200 + db SODA_POP + money 300 + db LEMONADE + money 350 diff --git a/de/engine/oak_speech2.asm b/de/engine/oak_speech2.asm new file mode 100755 index 00000000..ffb06477 --- /dev/null +++ b/de/engine/oak_speech2.asm @@ -0,0 +1,272 @@ +ChoosePlayerName: + call OakSpeechSlidePicRight + ld de, DefaultNamesPlayer + call DisplayIntroNameTextBox + ld a, [wCurrentMenuItem] + and a + jr z, .customName + ld hl, DefaultNamesPlayerList + call GetDefaultName + ld de, wPlayerName + call OakSpeechSlidePicLeft + jr .done +.customName + ld hl, wPlayerName + xor a ; NAME_PLAYER_SCREEN + ld [wNamingScreenType], a + call DisplayNamingScreen + ld a, [wcf50] + cp "@" + jr z, .customName + call ClearScreen + call Delay3 + ld de, RedPicFront + ld b, BANK(RedPicFront) + call IntroDisplayPicCenteredOrUpperRight +.done + ld hl, YourNameIsText + jp PrintText + +YourNameIsText: + TX_FAR _YourNameIsText + db "@" + +ChooseRivalName: + call OakSpeechSlidePicRight + ld de, DefaultNamesRival + call DisplayIntroNameTextBox + ld a, [wCurrentMenuItem] + and a + jr z, .customName + ld hl, DefaultNamesRivalList + call GetDefaultName + ld de, wRivalName + call OakSpeechSlidePicLeft + jr .done +.customName + ld hl, wRivalName + ld a, NAME_RIVAL_SCREEN + ld [wNamingScreenType], a + call DisplayNamingScreen + ld a, [wcf50] + cp "@" + jr z, .customName + call ClearScreen + call Delay3 + ld de, Rival1Pic + ld b, $13 + call IntroDisplayPicCenteredOrUpperRight +.done + ld hl, HisNameIsText + jp PrintText + +HisNameIsText: + TX_FAR _HisNameIsText + db "@" + +OakSpeechSlidePicLeft: + push de + coord hl, 0, 0 + lb bc, 12, 11 + call ClearScreenArea ; clear the name list text box + ld c, 10 + call DelayFrames + pop de + ld hl, wcd6d + ld bc, NAME_LENGTH + call CopyData + call Delay3 + coord hl, 12, 4 + lb de, 6, 6 * SCREEN_WIDTH + 5 + ld a, $ff + jr OakSpeechSlidePicCommon + +OakSpeechSlidePicRight: + coord hl, 5, 4 + lb de, 6, 6 * SCREEN_WIDTH + 5 + xor a + +OakSpeechSlidePicCommon: + push hl + push de + push bc + ld [hSlideDirection], a + ld a, d + ld [hSlideAmount], a + ld a, e + ld [hSlidingRegionSize], a + ld c, a + ld a, [hSlideDirection] + and a + jr nz, .next +; If sliding right, point hl to the end of the pic's tiles. + ld d, 0 + add hl, de +.next + ld d, h + ld e, l +.loop + xor a + ld [H_AUTOBGTRANSFERENABLED], a + ld a, [hSlideDirection] + and a + jr nz, .slideLeft +; sliding right + ld a, [hli] + ld [hld], a + dec hl + jr .next2 +.slideLeft + ld a, [hld] + ld [hli], a + inc hl +.next2 + dec c + jr nz, .loop + ld a, [hSlideDirection] + and a + jr z, .next3 +; If sliding left, we need to zero the last tile in the pic (there is no need +; to take a corresponding action when sliding right because hl initially points +; to a 0 tile in that case). + xor a + dec hl + ld [hl], a +.next3 + ld a, 1 + ld [H_AUTOBGTRANSFERENABLED], a + call Delay3 + ld a, [hSlidingRegionSize] + ld c, a + ld h, d + ld l, e + ld a, [hSlideDirection] + and a + jr nz, .slideLeft2 + inc hl + jr .next4 +.slideLeft2 + dec hl +.next4 + ld d, h + ld e, l + ld a, [hSlideAmount] + dec a + ld [hSlideAmount], a + jr nz, .loop + pop bc + pop de + pop hl + ret + +DisplayIntroNameTextBox: + push de + coord hl, 0, 0 + ld b, $a + ld c, $9 + call TextBoxBorder + coord hl, 3, 0 + ld de, .namestring + call PlaceString + pop de + coord hl, 2, 2 + call PlaceString + call UpdateSprites + xor a + ld [wCurrentMenuItem], a + ld [wLastMenuItem], a + inc a + ld [wTopMenuItemX], a + ld [wMenuWatchedKeys], a ; A_BUTTON + inc a + ld [wTopMenuItemY], a + inc a + ld [wMaxMenuItem], a + jp HandleMenuInput + +.namestring + db "NAME@" + +IF DEF(_RED) +DefaultNamesPlayer: + db "NAME" + next "ROT" + next "ASH" + next "JACK" + db "@" + +DefaultNamesRival: + db "NAME" + next "BLAU" + next "GARY" + next "JOHN" + db "@" +ENDC + +IF DEF(_BLUE) +DefaultNamesPlayer: + db "NAME" + next "BLAU" + next "GARY" + next "JOHN" + db "@" + +DefaultNamesRival: + db "NAME" + next "ROT" + next "ASH" + next "JACK" + db "@" +ENDC + +GetDefaultName: +; a = name index +; hl = name list + ld b, a + ld c, 0 +.loop + ld d, h + ld e, l +.innerLoop + ld a, [hli] + cp "@" + jr nz, .innerLoop + ld a, b + cp c + jr z, .foundName + inc c + jr .loop +.foundName + ld h, d + ld l, e + ld de, wcd6d + ld bc, $14 + jp CopyData + +IF DEF(_RED) +DefaultNamesPlayerList: + db "NAME@" + db "ROT@" + db "ASH@" + db "JACK@" +DefaultNamesRivalList: + db "NAME@" + db "BLAU@" + db "GARY@" + db "JOHN@" +ENDC +IF DEF(_BLUE) +DefaultNamesPlayerList: + db "NAME@" + db "BLAU@" + db "GARY@" + db "JOHN@" +DefaultNamesRivalList: + db "NAME@" + db "ROT@" + db "ASH@" + db "JACK@" +ENDC + +TextTerminator_6b20: + db "@" diff --git a/de/engine/overworld/movement.asm b/de/engine/overworld/movement.asm new file mode 100644 index 00000000..3b351e58 --- /dev/null +++ b/de/engine/overworld/movement.asm @@ -0,0 +1,893 @@ +UpdatePlayerSprite: + ld a, [wSpriteStateData2] + and a + jr z, .checkIfTextBoxInFrontOfSprite + cp $ff + jr z, .disableSprite + dec a + ld [wSpriteStateData2], a + jr .disableSprite +; check if a text box is in front of the sprite by checking if the lower left +; background tile the sprite is standing on is greater than $5F, which is +; the maximum number for map tiles +.checkIfTextBoxInFrontOfSprite + aCoord 8, 9 + ld [hTilePlayerStandingOn], a + cp $60 + jr c, .lowerLeftTileIsMapTile +.disableSprite + ld a, $ff + ld [wSpriteStateData1 + 2], a + ret +.lowerLeftTileIsMapTile + call DetectCollisionBetweenSprites + ld h, wSpriteStateData1 / $100 + ld a, [wWalkCounter] + and a + jr nz, .moving + ld a, [wPlayerMovingDirection] +; check if down + bit PLAYER_DIR_BIT_DOWN, a + jr z, .checkIfUp + xor a ; ld a, SPRITE_FACING_DOWN + jr .next +.checkIfUp + bit PLAYER_DIR_BIT_UP, a + jr z, .checkIfLeft + ld a, SPRITE_FACING_UP + jr .next +.checkIfLeft + bit PLAYER_DIR_BIT_LEFT, a + jr z, .checkIfRight + ld a, SPRITE_FACING_LEFT + jr .next +.checkIfRight + bit PLAYER_DIR_BIT_RIGHT, a + jr z, .notMoving + ld a, SPRITE_FACING_RIGHT + jr .next +.notMoving +; zero the animation counters + xor a + ld [wSpriteStateData1 + 7], a + ld [wSpriteStateData1 + 8], a + jr .calcImageIndex +.next + ld [wSpriteStateData1 + 9], a ; facing direction + ld a, [wFontLoaded] + bit 0, a + jr nz, .notMoving +.moving + ld a, [wd736] + bit 7, a ; is the player sprite spinning due to a spin tile? + jr nz, .skipSpriteAnim + ld a, [H_CURRENTSPRITEOFFSET] + add $7 + ld l, a + ld a, [hl] + inc a + ld [hl], a + cp 4 + jr nz, .calcImageIndex + xor a + ld [hl], a + inc hl + ld a, [hl] + inc a + and $3 + ld [hl], a +.calcImageIndex + ld a, [wSpriteStateData1 + 8] + ld b, a + ld a, [wSpriteStateData1 + 9] + add b + ld [wSpriteStateData1 + 2], a +.skipSpriteAnim +; If the player is standing on a grass tile, make the player's sprite have +; lower priority than the background so that it's partially obscured by the +; grass. Only the lower half of the sprite is permitted to have the priority +; bit set by later logic. + ld a, [hTilePlayerStandingOn] + ld c, a + ld a, [wGrassTile] + cp c + ld a, $0 + jr nz, .next2 + ld a, $80 +.next2 + ld [wSpriteStateData2 + 7], a + ret + +UnusedReadSpriteDataFunction: + push bc + push af + ld a, [H_CURRENTSPRITEOFFSET] + ld c, a + pop af + add c + ld l, a + pop bc + ret + +UpdateNPCSprite: + ld a, [H_CURRENTSPRITEOFFSET] + swap a + dec a + add a + ld hl, wMapSpriteData + add l + ld l, a + ld a, [hl] ; read movement byte 2 + ld [wCurSpriteMovement2], a + ld h, $c1 + ld a, [H_CURRENTSPRITEOFFSET] + ld l, a + inc l + ld a, [hl] ; c1x1 + and a + jp z, InitializeSpriteStatus + call CheckSpriteAvailability + ret c ; if sprite is invisible, on tile >=$60, in grass or player is currently walking + ld h, $c1 + ld a, [H_CURRENTSPRITEOFFSET] + ld l, a + inc l + ld a, [hl] ; c1x1 + bit 7, a ; is the face player flag set? + jp nz, MakeNPCFacePlayer + ld b, a + ld a, [wFontLoaded] + bit 0, a + jp nz, notYetMoving + ld a, b + cp $2 + jp z, UpdateSpriteMovementDelay ; c1x1 == 2 + cp $3 + jp z, UpdateSpriteInWalkingAnimation ; c1x1 == 3 + ld a, [wWalkCounter] + and a + ret nz ; don't do anything yet if player is currently moving (redundant, already tested in CheckSpriteAvailability) + call InitializeSpriteScreenPosition + ld h, $c2 + ld a, [H_CURRENTSPRITEOFFSET] + add $6 + ld l, a + ld a, [hl] ; c2x6: movement byte 1 + inc a + jr z, .randomMovement ; value $FF + inc a + jr z, .randomMovement ; value $FE +; scripted movement + dec a + ld [hl], a ; increment movement byte 1 (movement data index) + dec a + push hl + ld hl, wNPCNumScriptedSteps + dec [hl] ; decrement wNPCNumScriptedSteps + pop hl + ld de, wNPCMovementDirections + call LoadDEPlusA ; a = [wNPCMovementDirections + movement byte 1] + cp $e0 + jp z, ChangeFacingDirection + cp STAY + jr nz, .next +; reached end of wNPCMovementDirections list + ld [hl], a ; store $ff in movement byte 1, disabling scripted movement + ld hl, wd730 + res 0, [hl] + xor a + ld [wSimulatedJoypadStatesIndex], a + ld [wWastedByteCD3A], a + ret +.next + cp WALK + jr nz, .determineDirection +; current NPC movement data is $fe. this seems buggy + ld [hl], $1 ; set movement byte 1 to $1 + ld de, wNPCMovementDirections + call LoadDEPlusA ; a = [wNPCMovementDirections + $fe] (?) + jr .determineDirection +.randomMovement + call GetTileSpriteStandsOn + call Random +.determineDirection + ld b, a + ld a, [wCurSpriteMovement2] + cp $d0 + jr z, .moveDown ; movement byte 2 = $d0 forces down + cp $d1 + jr z, .moveUp ; movement byte 2 = $d1 forces up + cp $d2 + jr z, .moveLeft ; movement byte 2 = $d2 forces left + cp $d3 + jr z, .moveRight ; movement byte 2 = $d3 forces right + ld a, b + cp $40 ; a < $40: down (or left) + jr nc, .notDown + ld a, [wCurSpriteMovement2] + cp $2 + jr z, .moveLeft ; movement byte 2 = $2 only allows left or right +.moveDown + ld de, 2*SCREEN_WIDTH + add hl, de ; move tile pointer two rows down + lb de, 1, 0 + lb bc, 4, SPRITE_FACING_DOWN + jr TryWalking +.notDown + cp $80 ; $40 <= a < $80: up (or right) + jr nc, .notUp + ld a, [wCurSpriteMovement2] + cp $2 + jr z, .moveRight ; movement byte 2 = $2 only allows left or right +.moveUp + ld de, -2*SCREEN_WIDTH + add hl, de ; move tile pointer two rows up + lb de, -1, 0 + lb bc, 8, SPRITE_FACING_UP + jr TryWalking +.notUp + cp $c0 ; $80 <= a < $c0: left (or up) + jr nc, .notLeft + ld a, [wCurSpriteMovement2] + cp $1 + jr z, .moveUp ; movement byte 2 = $1 only allows up or down +.moveLeft + dec hl + dec hl ; move tile pointer two columns left + lb de, 0, -1 + lb bc, 2, SPRITE_FACING_LEFT + jr TryWalking +.notLeft ; $c0 <= a: right (or down) + ld a, [wCurSpriteMovement2] + cp $1 + jr z, .moveDown ; movement byte 2 = $1 only allows up or down +.moveRight + inc hl + inc hl ; move tile pointer two columns right + lb de, 0, 1 + lb bc, 1, SPRITE_FACING_RIGHT + jr TryWalking + +; changes facing direction by zeroing the movement delta and calling TryWalking +ChangeFacingDirection: + ld de, $0 + ; fall through + +; b: direction (1,2,4 or 8) +; c: new facing direction (0,4,8 or $c) +; d: Y movement delta (-1, 0 or 1) +; e: X movement delta (-1, 0 or 1) +; hl: pointer to tile the sprite would walk onto +; set carry on failure, clears carry on success +TryWalking: + push hl + ld h, $c1 + ld a, [H_CURRENTSPRITEOFFSET] + add $9 + ld l, a + ld [hl], c ; c1x9 (update facing direction) + ld a, [H_CURRENTSPRITEOFFSET] + add $3 + ld l, a + ld [hl], d ; c1x3 (update Y movement delta) + inc l + inc l + ld [hl], e ; c1x5 (update X movement delta) + pop hl + push de + ld c, [hl] ; read tile to walk onto + call CanWalkOntoTile + pop de + ret c ; cannot walk there (reinitialization of delay values already done) + ld h, $c2 + ld a, [H_CURRENTSPRITEOFFSET] + add $4 + ld l, a + ld a, [hl] ; c2x4: Y position + add d + ld [hli], a ; update Y position + ld a, [hl] ; c2x5: X position + add e + ld [hl], a ; update X position + ld a, [H_CURRENTSPRITEOFFSET] + ld l, a + ld [hl], $10 ; c2x0=16: walk animation counter + dec h + inc l + ld [hl], $3 ; c1x1: set movement status to walking + jp UpdateSpriteImage + +; update the walking animation parameters for a sprite that is currently walking +UpdateSpriteInWalkingAnimation: + ld a, [H_CURRENTSPRITEOFFSET] + add $7 + ld l, a + ld a, [hl] ; c1x7 (counter until next walk animation frame) + inc a + ld [hl], a ; c1x7 += 1 + cp $4 + jr nz, .noNextAnimationFrame + xor a + ld [hl], a ; c1x7 = 0 + inc l + ld a, [hl] ; c1x8 (walk animation frame) + inc a + and $3 + ld [hl], a ; advance to next animation frame every 4 ticks (16 ticks total for one step) +.noNextAnimationFrame + ld a, [H_CURRENTSPRITEOFFSET] + add $3 + ld l, a + ld a, [hli] ; c1x3 (movement Y delta) + ld b, a + ld a, [hl] ; c1x4 (screen Y position) + add b + ld [hli], a ; update screen Y position + ld a, [hli] ; c1x5 (movement X delta) + ld b, a + ld a, [hl] ; c1x6 (screen X position) + add b + ld [hl], a ; update screen X position + ld a, [H_CURRENTSPRITEOFFSET] + ld l, a + inc h + ld a, [hl] ; c2x0 (walk animation counter) + dec a + ld [hl], a ; update walk animation counter + ret nz + ld a, $6 ; walking finished, update state + add l + ld l, a + ld a, [hl] ; c2x6 (movement byte 1) + cp $fe + jr nc, .initNextMovementCounter ; values $fe and $ff + ld a, [H_CURRENTSPRITEOFFSET] + inc a + ld l, a + dec h + ld [hl], $1 ; c1x1 = 1 (movement status ready) + ret +.initNextMovementCounter + call Random + ld a, [H_CURRENTSPRITEOFFSET] + add $8 + ld l, a + ld a, [hRandomAdd] + and $7f + ld [hl], a ; c2x8: set next movement delay to a random value in [0,$7f] + dec h ; note that value 0 actually makes the delay $100 (bug?) + ld a, [H_CURRENTSPRITEOFFSET] + inc a + ld l, a + ld [hl], $2 ; c1x1 = 2 (movement status) + inc l + inc l + xor a + ld b, [hl] ; c1x3 (movement Y delta) + ld [hli], a ; reset movement Y delta + inc l + ld c, [hl] ; c1x5 (movement X delta) + ld [hl], a ; reset movement X delta + ret + +; update delay value (c2x8) for sprites in the delayed state (c1x1) +UpdateSpriteMovementDelay: + ld h, $c2 + ld a, [H_CURRENTSPRITEOFFSET] + add $6 + ld l, a + ld a, [hl] ; c2x6: movement byte 1 + inc l + inc l + cp $fe + jr nc, .tickMoveCounter ; values $fe or $ff + ld [hl], $0 + jr .moving +.tickMoveCounter + dec [hl] ; c2x8: frame counter until next movement + jr nz, notYetMoving +.moving + dec h + ld a, [H_CURRENTSPRITEOFFSET] + inc a + ld l, a + ld [hl], $1 ; c1x1 = 1 (mark as ready to move) +notYetMoving: + ld h, wSpriteStateData1 / $100 + ld a, [H_CURRENTSPRITEOFFSET] + add $8 + ld l, a + ld [hl], $0 ; c1x8 = 0 (walk animation frame) + jp UpdateSpriteImage + +MakeNPCFacePlayer: +; Make an NPC face the player if the player has spoken to him or her. + +; Check if the behaviour of the NPC facing the player when spoken to is +; disabled. This is only done when rubbing the S.S. Anne captain's back. + ld a, [wd72d] + bit 5, a + jr nz, notYetMoving + res 7, [hl] + ld a, [wPlayerDirection] + bit PLAYER_DIR_BIT_UP, a + jr z, .notFacingDown + ld c, SPRITE_FACING_DOWN + jr .facingDirectionDetermined +.notFacingDown + bit PLAYER_DIR_BIT_DOWN, a + jr z, .notFacingUp + ld c, SPRITE_FACING_UP + jr .facingDirectionDetermined +.notFacingUp + bit PLAYER_DIR_BIT_LEFT, a + jr z, .notFacingRight + ld c, SPRITE_FACING_RIGHT + jr .facingDirectionDetermined +.notFacingRight + ld c, SPRITE_FACING_LEFT +.facingDirectionDetermined + ld a, [H_CURRENTSPRITEOFFSET] + add $9 + ld l, a + ld [hl], c ; c1x9: set facing direction + jr notYetMoving + +InitializeSpriteStatus: + ld [hl], $1 ; $c1x1: set movement status to ready + inc l + ld [hl], $ff ; $c1x2: set sprite image to $ff (invisible/off screen) + inc h + ld a, [H_CURRENTSPRITEOFFSET] + add $2 + ld l, a + ld a, $8 + ld [hli], a ; $c2x2: set Y displacement to 8 + ld [hl], a ; $c2x3: set X displacement to 8 + call InitializeSpriteScreenPosition ; could have done fallthrough here + ret + +; calculates the sprite's screen position form its map position and the player position +InitializeSpriteScreenPosition: + ld h, wSpriteStateData2 / $100 + ld a, [H_CURRENTSPRITEOFFSET] + add $4 + ld l, a + ld a, [wYCoord] + ld b, a + ld a, [hl] ; c2x4 (Y position + 4) + sub b ; relative to player position + call Func_515D + sub $4 ; - 4 + dec h + ld [hli], a ; c1x4 (screen Y position) + inc h + ld a, [wXCoord] + ld b, a + ld a, [hli] ; c2x6 (X position + 4) + sub b ; relative to player position + call Func_515D + dec h + ld [hl], a ; c1x6 (screen X position) + ret + +Func_515D: ; 515D (1:515D) + jr nc, .asm_5166 + cpl + inc a + swap a + cpl + inc a + ret +.asm_5166 + swap a + ret + +; tests if sprite is off screen or otherwise unable to do anything +CheckSpriteAvailability: + predef IsObjectHidden + ld a, [$ffe5] + and a + jp nz, .spriteInvisible + ld h, wSpriteStateData2 / $100 + ld a, [H_CURRENTSPRITEOFFSET] + add $6 + ld l, a + ld a, [hl] ; c2x6: movement byte 1 + cp $fe + jr c, .skipXVisibilityTest ; movement byte 1 < $fe (i.e. the sprite's movement is scripted) + ld a, [H_CURRENTSPRITEOFFSET] + add $4 + ld l, a + ld b, [hl] ; c2x4: Y pos (+4) + ld a, [wYCoord] + cp b + jr z, .skipYVisibilityTest + jr nc, .spriteInvisible ; above screen region + add $8 ; screen is 9 tiles high + cp b + jr c, .spriteInvisible ; below screen region +.skipYVisibilityTest + inc l + ld b, [hl] ; c2x5: X pos (+4) + ld a, [wXCoord] + cp b + jr z, .skipXVisibilityTest + jr nc, .spriteInvisible ; left of screen region + add $9 ; screen is 10 tiles wide + cp b + jr c, .spriteInvisible ; right of screen region +.skipXVisibilityTest +; make the sprite invisible if a text box is in front of it +; $5F is the maximum number for map tiles + call GetTileSpriteStandsOn + ld d, $60 + ld a, [hli] + cp d + jr nc, .spriteInvisible ; standing on tile with ID >=$60 (bottom left tile) + ld a, [hld] + cp d + jr nc, .spriteInvisible ; standing on tile with ID >=$60 (bottom right tile) + ld bc, -20 + add hl, bc ; go back one row of tiles + ld a, [hli] + cp d + jr nc, .spriteInvisible ; standing on tile with ID >=$60 (top left tile) + ld a, [hl] + cp d + jr c, .spriteVisible ; standing on tile with ID >=$60 (top right tile) +.spriteInvisible + ld h, wSpriteStateData1 / $100 + ld a, [H_CURRENTSPRITEOFFSET] + add $2 + ld l, a + ld [hl], $ff ; c1x2 + scf + jr .done +.spriteVisible + ld c, a + ld a, [wWalkCounter] + and a + jr nz, .done ; if player is currently walking, we're done + call UpdateSpriteImage + inc h + ld a, [H_CURRENTSPRITEOFFSET] + add $7 + ld l, a + ld a, [wGrassTile] + cp c + ld a, $0 + jr nz, .notInGrass + ld a, $80 +.notInGrass + ld [hl], a ; c2x7 + and a +.done + ret + +UpdateSpriteImage: + ld h, $c1 + ld a, [H_CURRENTSPRITEOFFSET] + add $8 + ld l, a + ld a, [hli] ; c1x8: walk animation frame + ld b, a + ld a, [hl] ; c1x9: facing direction + add b + ld b, a + ld a, [$ff93] ; current sprite offset + add b + ld b, a + ld a, [H_CURRENTSPRITEOFFSET] + add $2 + ld l, a + ld [hl], b ; c1x2: sprite to display + ret + +; tests if sprite can walk the specified direction +; b: direction (1,2,4 or 8) +; c: ID of tile the sprite would walk onto +; d: Y movement delta (-1, 0 or 1) +; e: X movement delta (-1, 0 or 1) +; set carry on failure, clears carry on success +CanWalkOntoTile: + ld h, wSpriteStateData2 / $100 + ld a, [H_CURRENTSPRITEOFFSET] + add $6 + ld l, a + ld a, [hl] ; c2x6 (movement byte 1) + cp $fe + jr nc, .notScripted ; values $fe and $ff +; always allow walking if the movement is scripted + and a + ret +.notScripted + ld a, [wTilesetCollisionPtr] + ld l, a + ld a, [wTilesetCollisionPtr+1] + ld h, a +.tilePassableLoop + ld a, [hli] + cp $ff + jr z, .impassable + cp c + jr nz, .tilePassableLoop + ld h, $c2 + ld a, [H_CURRENTSPRITEOFFSET] + add $6 + ld l, a + ld a, [hl] ; $c2x6 (movement byte 1) + inc a + jr z, .impassable ; if $ff, no movement allowed (however, changing direction is) + ld h, wSpriteStateData1 / $100 + ld a, [H_CURRENTSPRITEOFFSET] + add $4 + ld l, a + ld a, [hli] ; c1x4 (screen Y pos) + add $4 ; align to blocks (Y pos is always 4 pixels off) + add d ; add Y delta + cp $80 ; if value is >$80, the destination is off screen (either $81 or $FF underflow) + jr nc, .impassable ; don't walk off screen + inc l + ld a, [hl] ; c1x6 (screen X pos) + add e ; add X delta + cp $90 ; if value is >$90, the destination is off screen (either $91 or $FF underflow) + jr nc, .impassable ; don't walk off screen + push de + push bc + call DetectCollisionBetweenSprites + pop bc + pop de + ld h, wSpriteStateData1 / $100 + ld a, [H_CURRENTSPRITEOFFSET] + add $c + ld l, a + ld a, [hl] ; c1xc (directions in which sprite collision would occur) + and b ; check against chosen direction (1,2,4 or 8) + jr nz, .impassable ; collision between sprites, don't go there + ld h, wSpriteStateData2 / $100 + ld a, [H_CURRENTSPRITEOFFSET] + add $2 + ld l, a + ld a, [hli] ; c2x2 (sprite Y displacement, initialized at $8, keep track of where a sprite did go) + bit 7, d ; check if going upwards (d=$ff) + jr nz, .upwards + add d + cp $5 + jr c, .impassable ; if c2x2+d < 5, don't go ;bug: this tests probably were supposed to prevent sprites + jr .checkHorizontal ; from walking out too far, but this line makes sprites get stuck +.upwards ; whenever they walked upwards 5 steps + sub $1 ; on the other hand, the amount a sprite can walk out to the + jr c, .impassable ; if d2x2 == 0, don't go ; right of bottom is not limited (until the counter overflows) +.checkHorizontal + ld d, a + ld a, [hl] ; c2x3 (sprite X displacement, initialized at $8, keep track of where a sprite did go) + bit 7, e ; check if going left (e=$ff) + jr nz, .left + add e + cp $5 ; compare, but no conditional jump like in the vertical check above (bug?) + jr .passable +.left + sub $1 + jr c, .impassable ; if d2x3 == 0, don't go +.passable + ld [hld], a ; update c2x3 + ld [hl], d ; update c2x2 + and a ; clear carry (marking success) + ret +.impassable + ld h, $c1 + ld a, [H_CURRENTSPRITEOFFSET] + inc a + ld l, a + ld [hl], $2 ; c1x1 = 2 (set movement status to delayed) + inc l + inc l + xor a + ld [hli], a ; c1x3 = 0 (clear Y movement delta) + inc l + ld [hl], a ; c1x5 = 0 (clear X movement delta) + inc h + ld a, [H_CURRENTSPRITEOFFSET] + add $8 + ld l, a + call Random + ld a, [hRandomAdd] + and $7f + ld [hl], a ; c2x8: set next movement delay to a random value in [0,$7f] (again with delay $100 if value is 0) + scf ; set carry (marking failure to walk) + ret + +; calculates the tile pointer pointing to the tile the current sprite stands on +; this is always the lower left tile of the 2x2 tile blocks all sprites are snapped to +; hl: output pointer +GetTileSpriteStandsOn: + ld h, wSpriteStateData1 / $100 + ld a, [H_CURRENTSPRITEOFFSET] + add $4 + ld l, a + ld a, [hli] ; c1x4: screen Y position + add $4 ; align to 2*2 tile blocks (Y position is always off 4 pixels to the top) + and $f8 ; in case object is currently moving + srl a ; screen Y tile * 4 + ld c, a + ld b, $0 + inc l + ld a, [hl] ; c1x6: screen Y position + srl a + srl a + srl a ; screen X tile + add SCREEN_WIDTH ; screen X tile + 20 + ld d, $0 + ld e, a + coord hl, 0, 0 + add hl, bc + add hl, bc + add hl, bc + add hl, bc + add hl, bc + add hl, de ; wTileMap + 20*(screen Y tile + 1) + screen X tile + ret + +; loads [de+a] into a +LoadDEPlusA: + add e + ld e, a + jr nc, .noCarry + inc d +.noCarry + ld a, [de] + ret + +DoScriptedNPCMovement: +; This is an alternative method of scripting an NPC's movement and is only used +; a few times in the game. It is used when the NPC and player must walk together +; in sync, such as when the player is following the NPC somewhere. An NPC can't +; be moved in sync with the player using the other method. + ld a, [wd730] + bit 7, a + ret z + ld hl, wd72e + bit 7, [hl] + set 7, [hl] + jp z, InitScriptedNPCMovement + ld hl, wNPCMovementDirections2 + ld a, [wNPCMovementDirections2Index] + add l + ld l, a + jr nc, .noCarry + inc h +.noCarry + ld a, [hl] +; check if moving up + cp NPC_MOVEMENT_UP + jr nz, .checkIfMovingDown + call GetSpriteScreenYPointer + ld c, SPRITE_FACING_UP + ld a, -2 + jr .move +.checkIfMovingDown + cp NPC_MOVEMENT_DOWN + jr nz, .checkIfMovingLeft + call GetSpriteScreenYPointer + ld c, SPRITE_FACING_DOWN + ld a, 2 + jr .move +.checkIfMovingLeft + cp NPC_MOVEMENT_LEFT + jr nz, .checkIfMovingRight + call GetSpriteScreenXPointer + ld c, SPRITE_FACING_LEFT + ld a, -2 + jr .move +.checkIfMovingRight + cp NPC_MOVEMENT_RIGHT + jr nz, .noMatch + call GetSpriteScreenXPointer + ld c, SPRITE_FACING_RIGHT + ld a, 2 + jr .move +.noMatch + cp $ff + ret +.move + ld b, a + ld a, [hl] + add b + ld [hl], a + ld a, [H_CURRENTSPRITEOFFSET] + add $9 + ld l, a + ld a, c + ld [hl], a ; facing direction + call AnimScriptedNPCMovement + ld hl, wScriptedNPCWalkCounter + dec [hl] + ret nz + ld a, 8 + ld [wScriptedNPCWalkCounter], a + ld hl, wNPCMovementDirections2Index + inc [hl] + ret + +InitScriptedNPCMovement: + xor a + ld [wNPCMovementDirections2Index], a + ld a, 8 + ld [wScriptedNPCWalkCounter], a + jp AnimScriptedNPCMovement + +GetSpriteScreenYPointer: + ld a, $4 + ld b, a + jr GetSpriteScreenXYPointerCommon + +GetSpriteScreenXPointer: + ld a, $6 + ld b, a + +GetSpriteScreenXYPointerCommon: + ld hl, wSpriteStateData1 + ld a, [H_CURRENTSPRITEOFFSET] + add l + add b + ld l, a + ret + +AnimScriptedNPCMovement: + ld hl, wSpriteStateData2 + ld a, [H_CURRENTSPRITEOFFSET] + add $e + ld l, a + ld a, [hl] ; VRAM slot + dec a + swap a + ld b, a + ld hl, wSpriteStateData1 + ld a, [H_CURRENTSPRITEOFFSET] + add $9 + ld l, a + ld a, [hl] ; facing direction + cp SPRITE_FACING_DOWN + jr z, .anim + cp SPRITE_FACING_UP + jr z, .anim + cp SPRITE_FACING_LEFT + jr z, .anim + cp SPRITE_FACING_RIGHT + jr z, .anim + ret +.anim + add b + ld b, a + ld [hSpriteVRAMSlotAndFacing], a + call AdvanceScriptedNPCAnimFrameCounter + ld hl, wSpriteStateData1 + ld a, [H_CURRENTSPRITEOFFSET] + add $2 + ld l, a + ld a, [hSpriteVRAMSlotAndFacing] + ld b, a + ld a, [hSpriteAnimFrameCounter] + add b + ld [hl], a + ret + +AdvanceScriptedNPCAnimFrameCounter: + ld a, [H_CURRENTSPRITEOFFSET] + add $7 + ld l, a + ld a, [hl] ; intra-animation frame counter + inc a + ld [hl], a + cp 4 + ret nz + xor a + ld [hl], a ; reset intra-animation frame counter + inc l + ld a, [hl] ; animation frame counter + inc a + and $3 + ld [hl], a + ld [hSpriteAnimFrameCounter], a + ret diff --git a/de/engine/overworld/pokemart.asm b/de/engine/overworld/pokemart.asm new file mode 100755 index 00000000..a8f3fae6 --- /dev/null +++ b/de/engine/overworld/pokemart.asm @@ -0,0 +1,272 @@ +DisplayPokemartDialogue_: + ld a,[wListScrollOffset] + ld [wSavedListScrollOffset],a + call UpdateSprites + xor a + ld [wBoughtOrSoldItemInMart],a +.loop + xor a + ld [wListScrollOffset],a + ld [wCurrentMenuItem],a + ld [wPlayerMonNumber],a + inc a + ld [wPrintItemPrices],a + ld a,MONEY_BOX + ld [wTextBoxID],a + call DisplayTextBoxID + ld a,BUY_SELL_QUIT_MENU + ld [wTextBoxID],a + call DisplayTextBoxID + +; This code is useless. It copies the address of the pokemart's inventory to hl, +; but the address is never used. + ld hl,wItemListPointer + ld a,[hli] + ld l,[hl] + ld h,a + + ld a,[wMenuExitMethod] + cp a,CANCELLED_MENU + jp z,.done + ld a,[wChosenMenuItem] + and a ; buying? + jp z,.buyMenu + dec a ; selling? + jp z,.sellMenu + dec a ; quitting? + jp z,.done +.sellMenu + +; the same variables are set again below, so this code has no effect + xor a + ld [wPrintItemPrices],a + ld a,INIT_BAG_ITEM_LIST + ld [wInitListType],a + callab InitList + + ld a,[wNumBagItems] + and a + jp z,.bagEmpty + ld hl,PokemonSellingGreetingText + call PrintText + call SaveScreenTilesToBuffer1 ; save screen +.sellMenuLoop + call LoadScreenTilesFromBuffer1 ; restore saved screen + ld a,MONEY_BOX + ld [wTextBoxID],a + call DisplayTextBoxID ; draw money text box + ld hl,wNumBagItems + ld a,l + ld [wListPointer],a + ld a,h + ld [wListPointer + 1],a + xor a + ld [wPrintItemPrices],a + ld [wCurrentMenuItem],a + ld a,ITEMLISTMENU + ld [wListMenuID],a + call DisplayListMenuID + jp c,.returnToMainPokemartMenu ; if the player closed the menu +.confirmItemSale ; if the player is trying to sell a specific item + call IsKeyItem + ld a,[wIsKeyItem] + and a + jr nz,.unsellableItem + ld a,[wcf91] + call IsItemHM + jr c,.unsellableItem + ld a,PRICEDITEMLISTMENU + ld [wListMenuID],a + ld [hHalveItemPrices],a ; halve prices when selling + call DisplayChooseQuantityMenu + inc a + jr z,.sellMenuLoop ; if the player closed the choose quantity menu with the B button + ld hl,PokemartTellSellPriceText + lb bc, 14, 1 ; location that PrintText always prints to, this is useless + call PrintText + coord hl, 13, 7 + lb bc, 8, 14 + ld a,TWO_OPTION_MENU + ld [wTextBoxID],a + call DisplayTextBoxID ; yes/no menu + ld a,[wMenuExitMethod] + cp a,CHOSE_SECOND_ITEM + jr z,.sellMenuLoop ; if the player chose No or pressed the B button + +; The following code is supposed to check if the player chose No, but the above +; check already catches it. + ld a,[wChosenMenuItem] + dec a + jr z,.sellMenuLoop + +.sellItem + ld a,[wBoughtOrSoldItemInMart] + and a + jr nz,.skipSettingFlag1 + inc a + ld [wBoughtOrSoldItemInMart],a +.skipSettingFlag1 + call AddAmountSoldToMoney + ld hl,wNumBagItems + call RemoveItemFromInventory + jp .sellMenuLoop +.unsellableItem + ld hl,PokemartUnsellableItemText + call PrintText + jp .returnToMainPokemartMenu +.bagEmpty + ld hl,PokemartItemBagEmptyText + call PrintText + call SaveScreenTilesToBuffer1 + jp .returnToMainPokemartMenu +.buyMenu + +; the same variables are set again below, so this code has no effect + ld a,1 + ld [wPrintItemPrices],a + ld a,INIT_OTHER_ITEM_LIST + ld [wInitListType],a + callab InitList + + ld hl,PokemartBuyingGreetingText + call PrintText + call SaveScreenTilesToBuffer1 +.buyMenuLoop + call LoadScreenTilesFromBuffer1 + ld a,MONEY_BOX + ld [wTextBoxID],a + call DisplayTextBoxID + ld hl,wItemList + ld a,l + ld [wListPointer],a + ld a,h + ld [wListPointer + 1],a + xor a + ld [wCurrentMenuItem],a + inc a + ld [wPrintItemPrices],a + inc a ; a = 2 (PRICEDITEMLISTMENU) + ld [wListMenuID],a + call DisplayListMenuID + jr c,.returnToMainPokemartMenu ; if the player closed the menu + ld a,99 + ld [wMaxItemQuantity],a + xor a + ld [hHalveItemPrices],a ; don't halve item prices when buying + call DisplayChooseQuantityMenu + inc a + jr z,.buyMenuLoop ; if the player closed the choose quantity menu with the B button + ld a,[wcf91] ; item ID + ld [wd11e],a ; store item ID for GetItemName + call GetItemName + call CopyStringToCF50 ; copy name to wcf50 + ld hl,PokemartTellBuyPriceText + call PrintText + coord hl, 13, 7 + lb bc, 8, 14 + ld a,TWO_OPTION_MENU + ld [wTextBoxID],a + call DisplayTextBoxID ; yes/no menu + ld a,[wMenuExitMethod] + cp a,CHOSE_SECOND_ITEM + jp z,.buyMenuLoop ; if the player chose No or pressed the B button + +; The following code is supposed to check if the player chose No, but the above +; check already catches it. + ld a,[wChosenMenuItem] + dec a + jr z,.buyMenuLoop + +.buyItem + call .isThereEnoughMoney + jr c,.notEnoughMoney + ld hl,wNumBagItems + call AddItemToInventory + jr nc,.bagFull + call SubtractAmountPaidFromMoney + ld a,[wBoughtOrSoldItemInMart] + and a + jr nz,.skipSettingFlag2 + ld a,1 + ld [wBoughtOrSoldItemInMart],a +.skipSettingFlag2 + ld a,SFX_PURCHASE + call PlaySoundWaitForCurrent + call WaitForSoundToFinish + ld hl,PokemartBoughtItemText + call PrintText + jp .buyMenuLoop +.returnToMainPokemartMenu + call LoadScreenTilesFromBuffer1 + ld a,MONEY_BOX + ld [wTextBoxID],a + call DisplayTextBoxID + ld hl,PokemartAnythingElseText + call PrintText + jp .loop +.isThereEnoughMoney + ld de,wPlayerMoney + ld hl,hMoney + ld c,3 ; length of money in bytes + jp StringCmp +.notEnoughMoney + ld hl,PokemartNotEnoughMoneyText + call PrintText + jr .returnToMainPokemartMenu +.bagFull + ld hl,PokemartItemBagFullText + call PrintText + jr .returnToMainPokemartMenu +.done + ld hl,PokemartThankYouText + call PrintText + ld a,1 + ld [wUpdateSpritesEnabled],a + call UpdateSprites + ld a,[wSavedListScrollOffset] + ld [wListScrollOffset],a + ret + +PokemartBuyingGreetingText: + TX_FAR _PokemartBuyingGreetingText + db "@" + +PokemartTellBuyPriceText: + TX_FAR _PokemartTellBuyPriceText + db "@" + +PokemartBoughtItemText: + TX_FAR _PokemartBoughtItemText + db "@" + +PokemartNotEnoughMoneyText: + TX_FAR _PokemartNotEnoughMoneyText + db "@" + +PokemartItemBagFullText: + TX_FAR _PokemartItemBagFullText + db "@" + +PokemonSellingGreetingText: + TX_FAR _PokemonSellingGreetingText + db "@" + +PokemartTellSellPriceText: + TX_FAR _PokemartTellSellPriceText + db "@" + +PokemartItemBagEmptyText: + TX_FAR _PokemartItemBagEmptyText + db "@" + +PokemartUnsellableItemText: + TX_FAR _PokemartUnsellableItemText + db "@" + +PokemartThankYouText: + TX_FAR _PokemartThankYouText + db "@" + +PokemartAnythingElseText: + TX_FAR _PokemartAnythingElseText + db "@" diff --git a/de/engine/print_waiting_text.asm b/de/engine/print_waiting_text.asm new file mode 100644 index 00000000..17b44a55 --- /dev/null +++ b/de/engine/print_waiting_text.asm @@ -0,0 +1,20 @@ +PrintWaitingText: + coord hl, 3, 10 + ld b, $1 + ld c, $d + ld a, [wIsInBattle] + and a + jr z, .asm_4c17 + call TextBoxBorder + jr .asm_4c1a +.asm_4c17 + call CableClub_TextBoxBorder +.asm_4c1a + coord hl, 4, 11 + ld de, WaitingText + call PlaceString + ld c, 50 + jp DelayFrames + +WaitingText: + db "BITTE WARTEN…@" diff --git a/de/engine/save.asm b/de/engine/save.asm new file mode 100755 index 00000000..d6ea8c7d --- /dev/null +++ b/de/engine/save.asm @@ -0,0 +1,708 @@ +LoadSAV: +;(if carry -> write +;"the file data is destroyed") + call ClearScreen + call LoadFontTilePatterns + call LoadTextBoxTilePatterns + call LoadSAV0 + jr c, .badsum + call LoadSAV1 + jr c, .badsum + call LoadSAV2 + jr c, .badsum + ld a, $2 ; good checksum + jr .goodsum +.badsum + ld hl, wd730 + push hl + set 6, [hl] + ld hl, FileDataDestroyedText + call PrintText + ld c, 100 + call DelayFrames + pop hl + res 6, [hl] + ld a, $1 ; bad checksum +.goodsum + ld [wSaveFileStatus], a + ret + +FileDataDestroyedText: + TX_FAR _FileDataDestroyedText + db "@" + +LoadSAV0: + ld a, SRAM_ENABLE + ld [MBC1SRamEnable], a + ld a, $1 + ld [MBC1SRamBankingMode], a + ld [MBC1SRamBank], a + ld hl, sPlayerName ; hero name located in SRAM + ld bc, sMainDataCheckSum - sPlayerName ; but here checks the full SAV + call SAVCheckSum + ld c, a + ld a, [sMainDataCheckSum] ; SAV's checksum + cp c + jp z, .checkSumsMatched + +; If the computed checksum didn't match the saved on, try again. + ld hl, sPlayerName + ld bc, sMainDataCheckSum - sPlayerName + call SAVCheckSum + ld c, a + ld a, [sMainDataCheckSum] ; SAV's checksum + cp c + jp nz, SAVBadCheckSum + +.checkSumsMatched + ld hl, sPlayerName + ld de, wPlayerName + ld bc, NAME_LENGTH + call CopyData + ld hl, sMainData + ld de, wMainDataStart + ld bc, wMainDataEnd - wMainDataStart + call CopyData + ld hl, wCurMapTileset + set 7, [hl] + ld hl, sSpriteData + ld de, wSpriteDataStart + ld bc, wSpriteDataEnd - wSpriteDataStart + call CopyData + ld a, [sTilesetType] + ld [hTilesetType], a + ld hl, sCurBoxData + ld de, wBoxDataStart + ld bc, wBoxDataEnd - wBoxDataStart + call CopyData + and a + jp SAVGoodChecksum + +LoadSAV1: + ld a, SRAM_ENABLE + ld [MBC1SRamEnable], a + ld a, $1 + ld [MBC1SRamBankingMode], a + ld [MBC1SRamBank], a + ld hl, sPlayerName ; hero name located in SRAM + ld bc, sMainDataCheckSum - sPlayerName ; but here checks the full SAV + call SAVCheckSum + ld c, a + ld a, [sMainDataCheckSum] ; SAV's checksum + cp c + jr nz, SAVBadCheckSum + ld hl, sCurBoxData + ld de, wBoxDataStart + ld bc, wBoxDataEnd - wBoxDataStart + call CopyData + and a + jp SAVGoodChecksum + +LoadSAV2: + ld a, SRAM_ENABLE + ld [MBC1SRamEnable], a + ld a, $1 + ld [MBC1SRamBankingMode], a + ld [MBC1SRamBank], a + ld hl, sPlayerName ; hero name located in SRAM + ld bc, sMainDataCheckSum - sPlayerName ; but here checks the full SAV + call SAVCheckSum + ld c, a + ld a, [sMainDataCheckSum] ; SAV's checksum + cp c + jp nz, SAVBadCheckSum + ld hl, sPartyData + ld de, wPartyDataStart + ld bc, wPartyDataEnd - wPartyDataStart + call CopyData + ld hl, sMainData + ld de, wPokedexOwned + ld bc, wPokedexSeenEnd - wPokedexOwned + call CopyData + and a + jp SAVGoodChecksum + +SAVBadCheckSum: + scf + +SAVGoodChecksum: + ld a, $0 + ld [MBC1SRamBankingMode], a + ld [MBC1SRamEnable], a + ret + +LoadSAVIgnoreBadCheckSum: +; unused function that loads save data and ignores bad checksums + call LoadSAV0 + call LoadSAV1 + jp LoadSAV2 + +SaveSAV: + callba PrintSaveScreenText + ld hl,WouldYouLikeToSaveText + call SaveSAVConfirm + and a ;|0 = Yes|1 = No| + ret nz + ld a,[wSaveFileStatus] + dec a + jr z,.save + call SAVCheckRandomID + jr z,.save + ld hl,OlderFileWillBeErasedText + call SaveSAVConfirm + and a + ret nz +.save + call SaveSAVtoSRAM + coord hl, 1, 13 + lb bc, 4, 18 + call ClearScreenArea + coord hl, 1, 14 + ld de,NowSavingString + call PlaceString + ld c,120 + call DelayFrames + ld hl,GameSavedText + call PrintText + ld a, SFX_SAVE + call PlaySoundWaitForCurrent + call WaitForSoundToFinish + ld c,30 + jp DelayFrames + +NowSavingString: + db "Speichern...@" + +SaveSAVConfirm: + call PrintText + coord hl, 0, 7 + lb bc, 8, 1 + ld a,TWO_OPTION_MENU + ld [wTextBoxID],a + call DisplayTextBoxID ; yes/no menu + ld a,[wCurrentMenuItem] + ret + +WouldYouLikeToSaveText: + TX_FAR _WouldYouLikeToSaveText + db "@" + +GameSavedText: + TX_FAR _GameSavedText + db "@" + +OlderFileWillBeErasedText: + TX_FAR _OlderFileWillBeErasedText + db "@" + +SaveSAVtoSRAM0: + ld a, SRAM_ENABLE + ld [MBC1SRamEnable], a + ld a, $1 + ld [MBC1SRamBankingMode], a + ld [MBC1SRamBank], a + ld hl, wPlayerName + ld de, sPlayerName + ld bc, NAME_LENGTH + call CopyData + ld hl, wMainDataStart + ld de, sMainData + ld bc, wMainDataEnd - wMainDataStart + call CopyData + ld hl, wSpriteDataStart + ld de, sSpriteData + ld bc, wSpriteDataEnd - wSpriteDataStart + call CopyData + ld hl, wBoxDataStart + ld de, sCurBoxData + ld bc, wBoxDataEnd - wBoxDataStart + call CopyData + ld a, [hTilesetType] + ld [sTilesetType], a + ld hl, sPlayerName + ld bc, sMainDataCheckSum - sPlayerName + call SAVCheckSum + ld [sMainDataCheckSum], a + xor a + ld [MBC1SRamBankingMode], a + ld [MBC1SRamEnable], a + ret + +SaveSAVtoSRAM1: +; stored pokémon + ld a, SRAM_ENABLE + ld [MBC1SRamEnable], a + ld a, $1 + ld [MBC1SRamBankingMode], a + ld [MBC1SRamBank], a + ld hl, wBoxDataStart + ld de, sCurBoxData + ld bc, wBoxDataEnd - wBoxDataStart + call CopyData + ld hl, sPlayerName + ld bc, sMainDataCheckSum - sPlayerName + call SAVCheckSum + ld [sMainDataCheckSum], a + xor a + ld [MBC1SRamBankingMode], a + ld [MBC1SRamEnable], a + ret + +SaveSAVtoSRAM2: + ld a, SRAM_ENABLE + ld [MBC1SRamEnable], a + ld a, $1 + ld [MBC1SRamBankingMode], a + ld [MBC1SRamBank], a + ld hl, wPartyDataStart + ld de, sPartyData + ld bc, wPartyDataEnd - wPartyDataStart + call CopyData + ld hl, wPokedexOwned ; pokédex only + ld de, sMainData + ld bc, wPokedexSeenEnd - wPokedexOwned + call CopyData + ld hl, sPlayerName + ld bc, sMainDataCheckSum - sPlayerName + call SAVCheckSum + ld [sMainDataCheckSum], a + xor a + ld [MBC1SRamBankingMode], a + ld [MBC1SRamEnable], a + ret + +SaveSAVtoSRAM: + ld a, $2 + ld [wSaveFileStatus], a + call SaveSAVtoSRAM0 + call SaveSAVtoSRAM1 + jp SaveSAVtoSRAM2 + +SAVCheckSum: +;Check Sum (result[1 byte] is complemented) + ld d, 0 +.loop + ld a, [hli] + add d + ld d, a + dec bc + ld a, b + or c + jr nz, .loop + ld a, d + cpl + ret + +CalcIndividualBoxCheckSums: + ld hl, sBox1 ; sBox7 + ld de, sBank2IndividualBoxChecksums ; sBank3IndividualBoxChecksums + ld b, NUM_BOXES / 2 +.loop + push bc + push de + ld bc, wBoxDataEnd - wBoxDataStart + call SAVCheckSum + pop de + ld [de], a + inc de + pop bc + dec b + jr nz, .loop + ret + +GetBoxSRAMLocation: +; in: a = box num +; out: b = box SRAM bank, hl = pointer to start of box + ld hl, BoxSRAMPointerTable + ld a, [wCurrentBoxNum] + and $7f + cp NUM_BOXES / 2 + ld b, 2 + jr c, .next + inc b + sub NUM_BOXES / 2 +.next + ld e, a + ld d, 0 + add hl, de + add hl, de + ld a, [hli] + ld h, [hl] + ld l, a + ret + +BoxSRAMPointerTable: + dw sBox1 ; sBox7 + dw sBox2 ; sBox8 + dw sBox3 ; sBox9 + dw sBox4 ; sBox10 + dw sBox5 ; sBox11 + dw sBox6 ; sBox12 + +ChangeBox:: + ld hl, WhenYouChangeBoxText + call PrintText + call YesNoChoice + ld a, [wCurrentMenuItem] + and a + ret nz ; return if No was chosen + ld hl, wCurrentBoxNum + bit 7, [hl] ; is it the first time player is changing the box? + call z, EmptyAllSRAMBoxes ; if so, empty all boxes in SRAM + call DisplayChangeBoxMenu + call UpdateSprites + ld hl, hFlags_0xFFF6 + set 1, [hl] + call HandleMenuInput + ld hl, hFlags_0xFFF6 + res 1, [hl] + bit 1, a ; pressed b + ret nz + call GetBoxSRAMLocation + ld e, l + ld d, h + ld hl, wBoxDataStart + call CopyBoxToOrFromSRAM ; copy old box from WRAM to SRAM + ld a, [wCurrentMenuItem] + set 7, a + ld [wCurrentBoxNum], a + call GetBoxSRAMLocation + ld de, wBoxDataStart + call CopyBoxToOrFromSRAM ; copy new box from SRAM to WRAM + ld hl, wMapTextPtr + ld de, wChangeBoxSavedMapTextPointer + ld a, [hli] + ld [de], a + inc de + ld a, [hl] + ld [de], a + call RestoreMapTextPointer + call SaveSAVtoSRAM + ld hl, wChangeBoxSavedMapTextPointer + call SetMapTextPointer + ld a, SFX_SAVE + call PlaySoundWaitForCurrent + call WaitForSoundToFinish + ret + +WhenYouChangeBoxText: + TX_FAR _WhenYouChangeBoxText + db "@" + +CopyBoxToOrFromSRAM: +; copy an entire box from hl to de with b as the SRAM bank + push hl + ld a, SRAM_ENABLE + ld [MBC1SRamEnable], a + ld a, $1 + ld [MBC1SRamBankingMode], a + ld a, b + ld [MBC1SRamBank], a + ld bc, wBoxDataEnd - wBoxDataStart + call CopyData + pop hl + +; mark the memory that the box was copied from as am empty box + xor a + ld [hli], a + dec a + ld [hl], a + + ld hl, sBox1 ; sBox7 + ld bc, sBank2AllBoxesChecksum - sBox1 + call SAVCheckSum + ld [sBank2AllBoxesChecksum], a ; sBank3AllBoxesChecksum + call CalcIndividualBoxCheckSums + xor a + ld [MBC1SRamBankingMode], a + ld [MBC1SRamEnable], a + ret + +DisplayChangeBoxMenu: + xor a + ld [H_AUTOBGTRANSFERENABLED], a + ld a, A_BUTTON | B_BUTTON + ld [wMenuWatchedKeys], a + ld a, 11 + ld [wMaxMenuItem], a + ld a, 1 + ld [wTopMenuItemY], a + ld a, 12 + ld [wTopMenuItemX], a + xor a + ld [wMenuWatchMovingOutOfBounds], a + ld a, [wCurrentBoxNum] + and $7f + ld [wCurrentMenuItem], a + ld [wLastMenuItem], a + coord hl, 0, 0 + ld b, 2 + ld c, 9 + call TextBoxBorder + ld hl, ChooseABoxText + call PrintText + coord hl, 11, 0 + ld b, 12 + ld c, 7 + call TextBoxBorder + ld hl, hFlags_0xFFF6 + set 2, [hl] + ld de, BoxNames + coord hl, 13, 1 + call PlaceString + ld hl, hFlags_0xFFF6 + res 2, [hl] + ld a, [wCurrentBoxNum] + and $7f + cp 9 + jr c, .singleDigitBoxNum + sub 9 + coord hl, 8, 2 + ld [hl], "1" + add "0" + jr .next +.singleDigitBoxNum + add "1" +.next + Coorda 9, 2 + coord hl, 1, 2 + ld de, BoxNoText + call PlaceString + call GetMonCountsForAllBoxes + coord hl, 18, 1 + ld de, wBoxMonCounts + ld bc, SCREEN_WIDTH + ld a, $c +.loop + push af + ld a, [de] + and a ; is the box empty? + jr z, .skipPlacingPokeball + ld [hl], $78 ; place pokeball tile next to box name if box not empty +.skipPlacingPokeball + add hl, bc + inc de + pop af + dec a + jr nz, .loop + ld a, 1 + ld [H_AUTOBGTRANSFERENABLED], a + ret + +ChooseABoxText: + TX_FAR _ChooseABoxText + db "@" + +BoxNames: + db "BOX 1" + next "BOX 2" + next "BOX 3" + next "BOX 4" + next "BOX 5" + next "BOX 6" + next "BOX 7" + next "BOX 8" + next "BOX 9" + next "BOX10" + next "BOX11" + next "BOX12@" + +BoxNoText: + db "BOX Nr.@" + +EmptyAllSRAMBoxes: +; marks all boxes in SRAM as empty (initialisation for the first time the +; player changes the box) + ld a, SRAM_ENABLE + ld [MBC1SRamEnable], a + ld a, $1 + ld [MBC1SRamBankingMode], a + ld a, 2 + ld [MBC1SRamBank], a + call EmptySRAMBoxesInBank + ld a, 3 + ld [MBC1SRamBank], a + call EmptySRAMBoxesInBank + xor a + ld [MBC1SRamBankingMode], a + ld [MBC1SRamEnable], a + ret + +EmptySRAMBoxesInBank: +; marks every box in the current SRAM bank as empty + ld hl, sBox1 ; sBox7 + call EmptySRAMBox + ld hl, sBox2 ; sBox8 + call EmptySRAMBox + ld hl, sBox3 ; sBox9 + call EmptySRAMBox + ld hl, sBox4 ; sBox10 + call EmptySRAMBox + ld hl, sBox5 ; sBox11 + call EmptySRAMBox + ld hl, sBox6 ; sBox12 + call EmptySRAMBox + ld hl, sBox1 ; sBox7 + ld bc, sBank2AllBoxesChecksum - sBox1 + call SAVCheckSum + ld [sBank2AllBoxesChecksum], a ; sBank3AllBoxesChecksum + call CalcIndividualBoxCheckSums + ret + +EmptySRAMBox: + xor a + ld [hli], a + dec a + ld [hl], a + ret + +GetMonCountsForAllBoxes: + ld hl, wBoxMonCounts + push hl + ld a, SRAM_ENABLE + ld [MBC1SRamEnable], a + ld a, $1 + ld [MBC1SRamBankingMode], a + ld a, $2 + ld [MBC1SRamBank], a + call GetMonCountsForBoxesInBank + ld a, $3 + ld [MBC1SRamBank], a + call GetMonCountsForBoxesInBank + xor a + ld [MBC1SRamBankingMode], a + ld [MBC1SRamEnable], a + pop hl + +; copy the count for the current box from WRAM + ld a, [wCurrentBoxNum] + and $7f + ld c, a + ld b, 0 + add hl, bc + ld a, [wNumInBox] + ld [hl], a + + ret + +GetMonCountsForBoxesInBank: + ld a, [sBox1] ; sBox7 + ld [hli], a + ld a, [sBox2] ; sBox8 + ld [hli], a + ld a, [sBox3] ; sBox9 + ld [hli], a + ld a, [sBox4] ; sBox10 + ld [hli], a + ld a, [sBox5] ; sBox11 + ld [hli], a + ld a, [sBox6] ; sBox12 + ld [hli], a + ret + +SAVCheckRandomID: +;checks if Sav file is the same by checking player's name 1st letter ($a598) +; and the two random numbers generated at game beginning +;(which are stored at wPlayerID)s + ld a,$0a + ld [MBC1SRamEnable],a + ld a,$01 + ld [MBC1SRamBankingMode],a + ld [MBC1SRamBank],a + ld a,[sPlayerName] + and a + jr z,.next + ld hl,sPlayerName + ld bc, sMainDataCheckSum - sPlayerName + call SAVCheckSum + ld c,a + ld a,[sMainDataCheckSum] + cp c + jr nz,.next + ld hl,sMainData + 98 ; player ID + ld a,[hli] + ld h,[hl] + ld l,a + ld a,[wPlayerID] + cp l + jr nz,.next + ld a,[wPlayerID + 1] + cp h +.next + ld a,$00 + ld [MBC1SRamBankingMode],a + ld [MBC1SRamEnable],a + ret + +SaveHallOfFameTeams: + ld a, [wNumHoFTeams] + dec a + cp HOF_TEAM_CAPACITY + jr nc, .shiftHOFTeams + ld hl, sHallOfFame + ld bc, HOF_TEAM + call AddNTimes + ld e, l + ld d, h + ld hl, wHallOfFame + ld bc, HOF_TEAM + jr HallOfFame_Copy + +.shiftHOFTeams +; if the space designated for HOF teams is full, then shift all HOF teams to the next slot, making space for the new HOF team +; this deletes the last HOF team though + ld hl, sHallOfFame + HOF_TEAM + ld de, sHallOfFame + ld bc, HOF_TEAM * (HOF_TEAM_CAPACITY - 1) + call HallOfFame_Copy + ld hl, wHallOfFame + ld de, sHallOfFame + HOF_TEAM * (HOF_TEAM_CAPACITY - 1) + ld bc, HOF_TEAM + jr HallOfFame_Copy + +LoadHallOfFameTeams: + ld hl, sHallOfFame + ld bc, HOF_TEAM + ld a, [wHoFTeamIndex] + call AddNTimes + ld de, wHallOfFame + ld bc, HOF_TEAM + ; fallthrough + +HallOfFame_Copy: + ld a, SRAM_ENABLE + ld [MBC1SRamEnable], a + ld a, $1 + ld [MBC1SRamBankingMode], a + xor a + ld [MBC1SRamBank], a + call CopyData + xor a + ld [MBC1SRamBankingMode], a + ld [MBC1SRamEnable], a + ret + +ClearSAV: + ld a, SRAM_ENABLE + ld [MBC1SRamEnable], a + ld a, $1 + ld [MBC1SRamBankingMode], a + xor a + call PadSRAM_FF + ld a, $1 + call PadSRAM_FF + ld a, $2 + call PadSRAM_FF + ld a, $3 + call PadSRAM_FF + xor a + ld [MBC1SRamBankingMode], a + ld [MBC1SRamEnable], a + ret + +PadSRAM_FF: + ld [MBC1SRamBank], a + ld hl, $a000 + ld bc, $2000 + ld a, $ff + jp FillMemory diff --git a/de/engine/slot_machine.asm b/de/engine/slot_machine.asm new file mode 100755 index 00000000..5e86237c --- /dev/null +++ b/de/engine/slot_machine.asm @@ -0,0 +1,892 @@ +PromptUserToPlaySlots: + call SaveScreenTilesToBuffer2 + ld a, BANK(DisplayTextIDInit) + ld [wAutoTextBoxDrawingControl], a + ld b, a + ld hl, DisplayTextIDInit + call Bankswitch + ld hl, PlaySlotMachineText + call PrintText + call YesNoChoice + ld a, [wCurrentMenuItem] + and a + jr nz, .done ; if player chose No + dec a + ld [wUpdateSpritesEnabled], a + ld hl, wSlotMachineRerollCounter + xor a + ld [hli], a + ld [hl], SMILE_BUBBLE + predef EmotionBubble + call GBPalWhiteOutWithDelay3 + call LoadSlotMachineTiles + call LoadFontTilePatterns + ld b, SET_PAL_SLOTS + call RunPaletteCommand + call GBPalNormal + ld a, $e4 + ld [rOBP0], a + ld hl, wd730 + set 6, [hl] + xor a + ld [wSlotMachineAllowMatchesCounter], a + ld hl, wStoppingWhichSlotMachineWheel + ld bc, $0014 + call FillMemory + call MainSlotMachineLoop + ld hl, wd730 + res 6, [hl] + xor a + ld [wSlotMachineAllowMatchesCounter], a + call GBPalWhiteOutWithDelay3 + ld a, $1 + ld [wUpdateSpritesEnabled], a + call RunDefaultPaletteCommand + call ReloadMapSpriteTilePatterns + call ReloadTilesetTilePatterns +.done + call LoadScreenTilesFromBuffer2 + call Delay3 + call GBPalNormal + ld a, [wSlotMachineSavedROMBank] + push af + jp CloseTextDisplay + +PlaySlotMachineText: + TX_FAR _PlaySlotMachineText + db "@" + +MainSlotMachineLoop: + call SlotMachine_PrintCreditCoins + xor a + ld hl, wPayoutCoins + ld [hli], a + ld [hl], a + call SlotMachine_PrintPayoutCoins + ld hl, BetHowManySlotMachineText + call PrintText + call SaveScreenTilesToBuffer1 +.loop + ld a, A_BUTTON | B_BUTTON + ld [wMenuWatchedKeys], a + ld a, 2 + ld [wMaxMenuItem], a + ld a, 12 + ld [wTopMenuItemY], a + ld a, 15 + ld [wTopMenuItemX], a + xor a + ld [wCurrentMenuItem], a + ld [wLastMenuItem], a + ld [wMenuWatchMovingOutOfBounds], a + coord hl, 14, 11 + ld b, 5 + ld c, 4 + call TextBoxBorder + coord hl, 16, 12 + ld de, CoinMultiplierSlotMachineText + call PlaceString + call HandleMenuInput + and B_BUTTON + jp nz, LoadScreenTilesFromBuffer1 + ld a, [wCurrentMenuItem] + ld b, a + ld a, 3 + sub b + ld [wSlotMachineBet], a + ld hl, wPlayerCoins + ld c, a + ld a, [hli] + and a + jr nz, .skip1 + ld a, [hl] + cp c + jr nc, .skip1 + ld hl, NotEnoughCoinsSlotMachineText + call PrintText + jr .loop +.skip1 + call LoadScreenTilesFromBuffer1 + call SlotMachine_SubtractBetFromPlayerCoins + call SlotMachine_LightBalls + call SlotMachine_SetFlags + ld a, 4 + ld hl, wSlotMachineWheel1SlipCounter + ld [hli], a + ld [hli], a + ld [hl], a + call WaitForSoundToFinish + ld a, SFX_SLOTS_NEW_SPIN + call PlaySound + ld hl, StartSlotMachineText + call PrintText + call SlotMachine_SpinWheels + call SlotMachine_CheckForMatches + ld hl, wPlayerCoins + ld a, [hli] + or [hl] + jr nz, .skip2 + ld hl, OutOfCoinsSlotMachineText + call PrintText + ld c, 60 + jp DelayFrames +.skip2 + ld hl, OneMoreGoSlotMachineText + call PrintText + coord hl, 13, 12 + lb bc, 13, 14 + xor a ; YES_NO_MENU + ld [wTwoOptionMenuID], a + ld a, TWO_OPTION_MENU + ld [wTextBoxID], a + call DisplayTextBoxID + ld a, [wCurrentMenuItem] + and a + ret nz + call SlotMachine_PutOutLitBalls + jp MainSlotMachineLoop + +CoinMultiplierSlotMachineText: + db "×3" + next "×2" + next "×1@" + +OutOfCoinsSlotMachineText: + TX_FAR _OutOfCoinsSlotMachineText + db "@" + +BetHowManySlotMachineText: + TX_FAR _BetHowManySlotMachineText + db "@" + +StartSlotMachineText: + TX_FAR _StartSlotMachineText + db "@" + +NotEnoughCoinsSlotMachineText: + TX_FAR _NotEnoughCoinsSlotMachineText + db "@" + +OneMoreGoSlotMachineText: + TX_FAR _OneMoreGoSlotMachineText + db "@" + +SlotMachine_SetFlags: + ld hl, wSlotMachineFlags + bit 7, [hl] + ret nz + ld a, [wSlotMachineAllowMatchesCounter] + and a + jr nz, .allowMatches + call Random + and a + jr z, .setAllowMatchesCounter ; 1/256 (~0.4%) chance + ld b, a + ld a, [wSlotMachineSevenAndBarModeChance] + cp b + jr c, .allowSevenAndBarMatches + ld a, 210 + cp b + jr c, .allowMatches ; 55/256 (~21.5%) chance + ld [hl], 0 + ret +.allowMatches + set 6, [hl] + ret +.setAllowMatchesCounter + ld a, 60 + ld [wSlotMachineAllowMatchesCounter], a + ret +.allowSevenAndBarMatches + set 7, [hl] + ret + +SlotMachine_SpinWheels: + ld c, 20 +.loop1 + push bc + call SlotMachine_AnimWheel1 + call SlotMachine_AnimWheel2 + call SlotMachine_AnimWheel3 + ld c, 2 + call DelayFrames + pop bc + dec c + jr nz, .loop1 + xor a + ld [wStoppingWhichSlotMachineWheel], a +.loop2 + call SlotMachine_HandleInputWhileWheelsSpin + call SlotMachine_StopOrAnimWheel1 + call SlotMachine_StopOrAnimWheel2 + call SlotMachine_StopOrAnimWheel3 + ret c + ld a, [wOnSGB] + xor $1 + inc a + ld c, a + call DelayFrames + jr .loop2 + +; Note that the wheels can only stop when a symbol is centred in the wheel +; and thus 3 full symbols rather than 2 full symbols and 2 half symbols are +; visible. The 3 functions below ensure this by checking if the wheel offset +; is even before stopping the wheel. + +SlotMachine_StopOrAnimWheel1: + ld a, [wStoppingWhichSlotMachineWheel] + cp 1 + jr c, .animWheel + ld de, wSlotMachineWheel1Offset + ld a, [de] + rra + jr nc, .animWheel ; check that a symbol is centred in the wheel + ld hl, wSlotMachineWheel1SlipCounter + ld a, [hl] + and a + ret z + dec [hl] + call SlotMachine_StopWheel1Early + ret nz +.animWheel + jp SlotMachine_AnimWheel1 + +SlotMachine_StopOrAnimWheel2: + ld a, [wStoppingWhichSlotMachineWheel] + cp 2 + jr c, .animWheel + ld de, wSlotMachineWheel2Offset + ld a, [de] + rra + jr nc, .animWheel ; check that a symbol is centred in the wheel + ld hl, wSlotMachineWheel2SlipCounter + ld a, [hl] + and a + ret z + dec [hl] + call SlotMachine_StopWheel2Early + ret z +.animWheel + jp SlotMachine_AnimWheel2 + +SlotMachine_StopOrAnimWheel3: + ld a, [wStoppingWhichSlotMachineWheel] + cp 3 + jr c, .animWheel + ld de, wSlotMachineWheel3Offset + ld a, [de] + rra + jr nc, .animWheel ; check that a symbol is centred in the wheel +; wheel 3 stops as soon as possible + scf + ret +.animWheel + call SlotMachine_AnimWheel3 + and a + ret + +SlotMachine_StopWheel1Early: + call SlotMachine_GetWheel1Tiles + ld hl, wSlotMachineWheel1BottomTile + ld a, [wSlotMachineFlags] + and $80 + jr nz, .sevenAndBarMode +; Stop early if the middle symbol is not a cherry. + inc hl + ld a, [hl] + cp SLOTSCHERRY >> 8 + jr nz, .stopWheel + ret +; It looks like this was intended to make the wheel stop when a 7 symbol was +; visible, but it has a bug and so the wheel stops randomly. +.sevenAndBarMode + ld c, $3 +.loop + ld a, [hli] + cp SLOTS7 >> 8 + jr c, .stopWheel ; condition never true + dec c + jr nz, .loop + ret +.stopWheel + inc a + ld hl, wSlotMachineWheel1SlipCounter + ld [hl], 0 + ret + +SlotMachine_StopWheel2Early: + call SlotMachine_GetWheel2Tiles + ld a, [wSlotMachineFlags] + and $80 + jr nz, .sevenAndBarMode +; Stop early if any symbols are lined up in the first two wheels. + call SlotMachine_FindWheel1Wheel2Matches + ret nz + jr .stopWheel +; Stop early if two 7 symbols or two bar symbols are lined up in the first two +; wheels OR if no symbols are lined up and the bottom symbol in wheel 2 is a +; 7 symbol or bar symbol. The second part could be a bug or a way to reduce the +; player's odds. +.sevenAndBarMode + call SlotMachine_FindWheel1Wheel2Matches + ld a, [de] + cp (SLOTSBAR >> 8) + 1 + ret nc +.stopWheel + xor a + ld [wSlotMachineWheel2SlipCounter], a + ret + +SlotMachine_FindWheel1Wheel2Matches: +; return whether wheel 1 and wheel 2's current positions allow a match (given +; that wheel 3 stops in a good position) in Z + ld hl, wSlotMachineWheel1BottomTile + ld de, wSlotMachineWheel2BottomTile + ld a, [de] + cp [hl] ; wheel 1 bottom, wheel 2 bottom + ret z + inc de + ld a, [de] + cp [hl] ; wheel 1 bottom, wheel 2 middle + ret z + inc hl + cp [hl] ; wheel 1 middle, wheel 2 middle + ret z + inc hl + cp [hl] ; wheel 1 top, wheel 2 middle + ret z + inc de + ld a, [de] + cp [hl] ; wheel 1 top, wheel 2 top + ret z + dec de + dec de + ret + +SlotMachine_CheckForMatches: + call SlotMachine_GetWheel3Tiles + ld a, [wSlotMachineBet] + cp 2 + jr z, .checkMatchesFor2CoinBet + cp 1 + jr z, .checkMatchFor1CoinBet +; 3 coin bet allows diagonal matches (plus the matches for 1/2 coin bets) + ld hl, wSlotMachineWheel1BottomTile + ld de, wSlotMachineWheel2MiddleTile + ld bc, wSlotMachineWheel3TopTile + call SlotMachine_CheckForMatch + jp z, .foundMatch + ld hl, wSlotMachineWheel1TopTile + ld de, wSlotMachineWheel2MiddleTile + ld bc, wSlotMachineWheel3BottomTile + call SlotMachine_CheckForMatch + jr z, .foundMatch +; 2 coin bet allows top/bottom horizontal matches (plus the match for a 1 coin bet) +.checkMatchesFor2CoinBet + ld hl, wSlotMachineWheel1TopTile + ld de, wSlotMachineWheel2TopTile + ld bc, wSlotMachineWheel3TopTile + call SlotMachine_CheckForMatch + jr z, .foundMatch + ld hl, wSlotMachineWheel1BottomTile + ld de, wSlotMachineWheel2BottomTile + ld bc, wSlotMachineWheel3BottomTile + call SlotMachine_CheckForMatch + jr z, .foundMatch +; 1 coin bet only allows a middle horizontal match +.checkMatchFor1CoinBet + ld hl, wSlotMachineWheel1MiddleTile + ld de, wSlotMachineWheel2MiddleTile + ld bc, wSlotMachineWheel3MiddleTile + call SlotMachine_CheckForMatch + jr z, .foundMatch + ld a, [wSlotMachineFlags] + and $c0 + jr z, .noMatch + ld hl, wSlotMachineRerollCounter + dec [hl] + jr nz, .rollWheel3DownByOneSymbol +.noMatch + ld hl, NotThisTimeText + call PrintText +.done + xor a + ld [wMuteAudioAndPauseMusic], a + ret +.rollWheel3DownByOneSymbol + call SlotMachine_AnimWheel3 + call DelayFrame + call SlotMachine_AnimWheel3 + call DelayFrame + jp SlotMachine_CheckForMatches +.foundMatch + ld a, [wSlotMachineFlags] + and $c0 + jr z, .rollWheel3DownByOneSymbol ; roll wheel if player isn't allowed to win + and $80 + jr nz, .acceptMatch +; if 7/bar matches aren't enabled and the match was a 7/bar symbol, roll wheel + ld a, [hl] + cp (SLOTSBAR >> 8) + 1 + jr c, .rollWheel3DownByOneSymbol +.acceptMatch + ld a, [hl] + sub $2 + ld [wSlotMachineWinningSymbol], a + ld hl, SlotRewardPointers + ld c, a + ld b, 0 + add hl, bc + ld a, [hli] + ld e, a + ld a, [hli] + ld d, a + push de + ld a, [hli] + ld h, [hl] + ld l, a + ld de, wcf50 + ld bc, 4 + call CopyData + pop hl + ld de, .flashScreenLoop + push de + jp hl + +.flashScreenLoop + ld a, [rBGP] + xor $40 + ld [rBGP], a + ld c, 5 + call DelayFrames + dec b + jr nz, .flashScreenLoop + ld hl, wPayoutCoins + ld [hl], d + inc hl + ld [hl], e + call SlotMachine_PrintPayoutCoins + ld hl, SymbolLinedUpSlotMachineText + call PrintText + call WaitForTextScrollButtonPress + call SlotMachine_PayCoinsToPlayer + call SlotMachine_PrintPayoutCoins + ld a, $e4 + ld [rOBP0], a + jp .done + +SymbolLinedUpSlotMachineText: + TX_ASM + push bc + call SlotMachine_PrintWinningSymbol + ld hl, LinedUpText + pop bc + inc bc + inc bc + inc bc + inc bc + ret + +LinedUpText: + TX_FAR _LinedUpText + db "@" + +SlotRewardPointers: + dw SlotReward300Func + dw SlotReward300Text + dw SlotReward100Func + dw SlotReward100Text + dw SlotReward8Func + dw SlotReward8Text + dw SlotReward15Func + dw SlotReward15Text + dw SlotReward15Func + dw SlotReward15Text + dw SlotReward15Func + dw SlotReward15Text + +SlotReward300Text: + db "300@" + +SlotReward100Text: + db "100@" + +SlotReward8Text: + db "8@" + +SlotReward15Text: + db "15@" + +NotThisTimeText: + TX_FAR _NotThisTimeText + db "@" + +; compares the slot machine tiles at bc, de, and hl +SlotMachine_CheckForMatch: + ld a, [de] + cp [hl] + ret nz + ld a, [bc] + cp [hl] + ret + +SlotMachine_GetWheel3Tiles: + ld de, wSlotMachineWheel3BottomTile + ld hl, SlotMachineWheel3 + ld a, [wSlotMachineWheel3Offset] + call SlotMachine_GetWheelTiles + +SlotMachine_GetWheel2Tiles: + ld de, wSlotMachineWheel2BottomTile + ld hl, SlotMachineWheel2 + ld a, [wSlotMachineWheel2Offset] + call SlotMachine_GetWheelTiles + +SlotMachine_GetWheel1Tiles: + ld de, wSlotMachineWheel1BottomTile + ld hl, SlotMachineWheel1 + ld a, [wSlotMachineWheel1Offset] + +SlotMachine_GetWheelTiles: + ld c, a + ld b, 0 + add hl, bc + ld c, 3 +.loop + ld a, [hli] + ld [de], a + inc de + inc hl + dec c + jr nz, .loop + ret + +SlotReward8Func: + ld hl, wSlotMachineAllowMatchesCounter + ld a, [hl] + and a + jr z, .skip + dec [hl] +.skip + ld b, $2 + ld de, 8 + ret + +SlotReward15Func: + ld hl, wSlotMachineAllowMatchesCounter + ld a, [hl] + and a + jr z, .skip + dec [hl] +.skip + ld b, $4 + ld de, 15 + ret + +SlotReward100Func: + ld a, SFX_GET_KEY_ITEM + call PlaySound + xor a + ld [wSlotMachineFlags], a + ld b, $8 + ld de, 100 + ret + +SlotReward300Func: + ld hl, YeahText + call PrintText + ld a, SFX_GET_ITEM_2 + call PlaySound + call Random + cp $80 + ld a, $0 + jr c, .skip + ld [wSlotMachineFlags], a +.skip + ld [wSlotMachineAllowMatchesCounter], a + ld b, $14 + ld de, 300 + ret + +YeahText: + TX_FAR _YeahText + TX_DELAY + db "@" + +SlotMachine_PrintWinningSymbol: +; prints winning symbol and down arrow in text box + coord hl, 2, 14 + ld a, [wSlotMachineWinningSymbol] + add $25 + ld [hli], a + inc a + ld [hld], a + inc a + ld de, -SCREEN_WIDTH + add hl, de + ld [hli], a + inc a + ld [hl], a + coord hl, 18, 16 + ld [hl], "▼" + ret + +SlotMachine_SubtractBetFromPlayerCoins: + ld hl, wTempCoins2 + 1 + ld a, [wSlotMachineBet] + ld [hld], a + xor a + ld [hli], a + ld de, wPlayerCoins + 1 + ld c, $2 + predef SubBCDPredef + +SlotMachine_PrintCreditCoins: + coord hl, 5, 1 + ld de, wPlayerCoins + ld c, $2 + jp PrintBCDNumber + +SlotMachine_PrintPayoutCoins: + coord hl, 11, 1 + ld de, wPayoutCoins + lb bc, LEADING_ZEROES | 2, 4 ; 2 bytes, 4 digits + jp PrintNumber + +SlotMachine_PayCoinsToPlayer: + ld a, $1 + ld [wMuteAudioAndPauseMusic], a + call WaitForSoundToFinish + +; Put 1 in the temp coins variable. This value is added to the player's coins +; repeatedly so the player can watch the value go up 1 coin at a time. + ld hl, wTempCoins1 + xor a + ld [hli], a + inc a + ld [hl], a + + ld a, 5 + ld [wAnimCounter], a + +; Subtract 1 from the payout amount and add 1 to the player's coins each +; iteration until the payout amount reaches 0. +.loop + ld a, [wPayoutCoins + 1] + ld l, a + ld a, [wPayoutCoins] + ld h, a + or l + ret z + ld de, -1 + add hl, de + ld a, l + ld [wPayoutCoins + 1], a + ld a, h + ld [wPayoutCoins], a + ld hl, wTempCoins1 + 1 + ld de, wPlayerCoins + 1 + ld c, $2 + predef AddBCDPredef + call SlotMachine_PrintCreditCoins + call SlotMachine_PrintPayoutCoins + ld a, SFX_SLOTS_REWARD + call PlaySound + ld a, [wAnimCounter] + dec a + jr nz, .skip1 + ld a, [rOBP0] + xor $40 ; make the slot wheel symbols flash + ld [rOBP0], a + ld a, 5 +.skip1 + ld [wAnimCounter], a + ld a, [wSlotMachineWinningSymbol] + cp (SLOTSBAR >> 8) + 1 + ld c, 8 + jr nc, .skip2 + srl c ; c = 4 (make the the coins transfer faster if the symbol was 7 or bar) +.skip2 + call DelayFrames + jr .loop + +SlotMachine_PutOutLitBalls: + ld a, $23 + ld [wNewSlotMachineBallTile], a + jr SlotMachine_UpdateThreeCoinBallTiles + +SlotMachine_LightBalls: + ld a, $14 + ld [wNewSlotMachineBallTile], a + ld a, [wSlotMachineBet] + dec a + jr z, SlotMachine_UpdateOneCoinBallTiles + dec a + jr z, SlotMachine_UpdateTwoCoinBallTiles + +SlotMachine_UpdateThreeCoinBallTiles: + coord hl, 3, 2 + call SlotMachine_UpdateBallTiles + coord hl, 3, 10 + call SlotMachine_UpdateBallTiles + +SlotMachine_UpdateTwoCoinBallTiles: + coord hl, 3, 4 + call SlotMachine_UpdateBallTiles + coord hl, 3, 8 + call SlotMachine_UpdateBallTiles + +SlotMachine_UpdateOneCoinBallTiles: + coord hl, 3, 6 + +SlotMachine_UpdateBallTiles: + ld a, [wNewSlotMachineBallTile] + ld [hl], a + ld bc, 13 + add hl, bc + ld [hl], a + ld bc, 7 + add hl, bc + inc a + ld [hl], a + ld bc, 13 + add hl, bc + ld [hl], a + ret + +SlotMachine_AnimWheel1: + ld bc, SlotMachineWheel1 + ld de, wSlotMachineWheel1Offset + ld hl, wOAMBuffer + ld a, $30 + ld [wBaseCoordX], a + jr SlotMachine_AnimWheel + +SlotMachine_AnimWheel2: + ld bc, SlotMachineWheel2 + ld de, wSlotMachineWheel2Offset + ld hl, wOAMBuffer + $30 + ld a, $50 + ld [wBaseCoordX], a + jr SlotMachine_AnimWheel + +SlotMachine_AnimWheel3: + ld bc, SlotMachineWheel3 + ld de, wSlotMachineWheel3Offset + ld hl, wOAMBuffer + $60 + ld a, $70 + ld [wBaseCoordX], a + +SlotMachine_AnimWheel: + ld a, $58 + ld [wBaseCoordY], a + push de + ld a, [de] + ld d, b + add c + ld e, a + jr nc, .loop + inc d +.loop + ld a, [wBaseCoordY] + ld [hli], a + ld a, [wBaseCoordX] + ld [hli], a + ld a, [de] + ld [hli], a + ld a, $80 + ld [hli], a + ld a, [wBaseCoordY] + ld [hli], a + ld a, [wBaseCoordX] + add $8 + ld [hli], a + ld a, [de] + inc a + ld [hli], a + ld a, $80 + ld [hli], a + inc de + ld a, [wBaseCoordY] + sub $8 + ld [wBaseCoordY], a + cp $28 + jr nz, .loop + pop de + ld a, [de] + inc a ; advance the offset so that the wheel animates + cp 30 + jr nz, .skip + xor a ; wrap around to 0 when the offset reaches 30 +.skip + ld [de], a + ret + +SlotMachine_HandleInputWhileWheelsSpin: + call DelayFrame + call JoypadLowSensitivity + ld a, [hJoy5] + and A_BUTTON + ret z + ld hl, wStoppingWhichSlotMachineWheel + ld a, [hl] + dec a + ld de, wSlotMachineWheel1SlipCounter + jr z, .skip + dec a + ld de, wSlotMachineWheel2SlipCounter + jr z, .skip +.loop + inc [hl] + ld a, SFX_SLOTS_STOP_WHEEL + jp PlaySound +.skip + ld a, [de] + and a + ret nz + jr .loop + +LoadSlotMachineTiles: + call DisableLCD + ld hl, SlotMachineTiles2 + ld de, vChars0 + ld bc, $1c0 + ld a, BANK(SlotMachineTiles2) + call FarCopyData2 + ld hl, SlotMachineTiles1 + ld de, vChars2 + ld bc, $250 + ld a, BANK(SlotMachineTiles1) + call FarCopyData2 + ld hl, SlotMachineTiles2 + ld de, vChars2 + $250 + ld bc, $1c0 + ld a, BANK(SlotMachineTiles2) + call FarCopyData2 + ld hl, SlotMachineMap + coord de, 0, 0 + ld bc, SlotMachineMapEnd - SlotMachineMap + call CopyData + call EnableLCD + ld hl, wSlotMachineWheel1Offset + ld a, $1c + ld [hli], a + ld [hli], a + ld [hl], a + call SlotMachine_AnimWheel1 + call SlotMachine_AnimWheel2 + jp SlotMachine_AnimWheel3 + +SlotMachineMap: + INCBIN "gfx/tilemaps/slotmachine.map" +SlotMachineMapEnd: + +INCLUDE "data/slot_machine_wheels.asm" + +SlotMachineTiles1: +IF DEF(_RED) + INCBIN "gfx/red/slotmachine1.2bpp" +ENDC +IF DEF(_BLUE) + INCBIN "gfx/blue/slotmachine1.2bpp" +ENDC diff --git a/de/engine/status_ailments.asm b/de/engine/status_ailments.asm new file mode 100755 index 00000000..c3925eab --- /dev/null +++ b/de/engine/status_ailments.asm @@ -0,0 +1,46 @@ +PrintStatusAilment: + ld a, [de] + bit PSN, a + jr nz, .psn + bit BRN, a + jr nz, .brn + bit FRZ, a + jr nz, .frz + bit PAR, a + jr nz, .par + and SLP + ret z + ld a, "S" + ld [hli], a + ld a, "L" + ld [hli], a + ld [hl], "F" + ret +.psn + ld a, "G" + ld [hli], a + ld a, "I" + ld [hli], a + ld [hl], "F" + ret +.brn + ld a, "B" + ld [hli], a + ld a, "R" + ld [hli], a + ld [hl], "T" + ret +.frz + ld a, "G" + ld [hli], a + ld a, "F" + ld [hli], a + ld [hl], "R" + ret +.par + ld a, "P" + ld [hli], a + ld a, "A" + ld [hli], a + ld [hl], "R" + ret diff --git a/de/engine/titlescreen.asm b/de/engine/titlescreen.asm new file mode 100755 index 00000000..a1e8d787 --- /dev/null +++ b/de/engine/titlescreen.asm @@ -0,0 +1,398 @@ +; copy text of fixed length NAME_LENGTH (like player name, rival name, mon names, ...) +CopyFixedLengthText: + ld bc, NAME_LENGTH + jp CopyData + +SetDefaultNamesBeforeTitlescreen: + ld hl, NintenText + ld de, wPlayerName + call CopyFixedLengthText + ld hl, SonyText + ld de, wRivalName + call CopyFixedLengthText + xor a + ld [hWY], a + ld [wLetterPrintingDelayFlags], a + ld hl, wd732 + ld [hli], a + ld [hli], a + ld [hl], a + ld a, BANK(Music_TitleScreen) + ld [wAudioROMBank], a + ld [wAudioSavedROMBank], a + +DisplayTitleScreen: + call GBPalWhiteOut + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + xor a + ld [hTilesetType], a + ld [hSCX], a + ld a, $40 + ld [hSCY], a + ld a, $90 + ld [hWY], a + call ClearScreen + call DisableLCD + call LoadFontTilePatterns + ld hl, NintendoCopyrightLogoGraphics + ld de, vTitleLogo2 + $100 + ld bc, $50 + ld a, BANK(NintendoCopyrightLogoGraphics) + call FarCopyData2 + ld hl, GamefreakLogoGraphics + ld de, vTitleLogo2 + $100 + $50 + ld bc, $a0 + ld a, BANK(GamefreakLogoGraphics) + call FarCopyData2 + ld hl, PokemonLogoGraphics + ld de, vTitleLogo + ld bc, $600 + ld a, BANK(PokemonLogoGraphics) + call FarCopyData2 ; first chunk + ld hl, PokemonLogoGraphics+$600 + ld de, vTitleLogo2 + ld bc, $100 + ld a, BANK(PokemonLogoGraphics) + call FarCopyData2 ; second chunk + ld hl, Version_GFX + ld de,vChars2 + $600 - (Version_GFXEnd - Version_GFX - $50) + ld bc, Version_GFXEnd - Version_GFX + ld a, BANK(Version_GFX) + call FarCopyDataDouble + call ClearBothBGMaps + +; place tiles for pokemon logo (except for the last row) + coord hl, 2, 1 + ld a, $80 + ld de, SCREEN_WIDTH + ld c, 6 +.pokemonLogoTileLoop + ld b, $10 + push hl +.pokemonLogoTileRowLoop ; place tiles for one row + ld [hli], a + inc a + dec b + jr nz, .pokemonLogoTileRowLoop + pop hl + add hl, de + dec c + jr nz, .pokemonLogoTileLoop + +; place tiles for the last row of the pokemon logo + coord hl, 2, 7 + ld a, $31 + ld b, $10 +.pokemonLogoLastTileRowLoop + ld [hli], a + inc a + dec b + jr nz, .pokemonLogoLastTileRowLoop + + call DrawPlayerCharacter + +; put a pokeball in the player's hand + ld hl, wOAMBuffer + $28 + ld a, $74 + ld [hl], a + +; place tiles for title screen copyright + coord hl, 2, 17 + ld de, .tileScreenCopyrightTiles + ld b, $10 +.tileScreenCopyrightTilesLoop + ld a, [de] + ld [hli], a + inc de + dec b + jr nz, .tileScreenCopyrightTilesLoop + + jr .next + +.tileScreenCopyrightTiles + db $41,$42,$43,$44,$42,$43,$4f,$46,$47,$48,$49,$4A,$4B,$4C,$4D,$4E ; ©1995-1999 GAME FREAK inc. + +.next + call SaveScreenTilesToBuffer2 + call LoadScreenTilesFromBuffer2 + call EnableLCD +IF DEF(_RED) + ld a,CHARMANDER ; which Pokemon to show first on the title screen +ENDC +IF DEF(_BLUE) + ld a,SQUIRTLE ; which Pokemon to show first on the title screen +ENDC + + ld [wTitleMonSpecies], a + call LoadTitleMonSprite + ld a, (vBGMap0 + $300) / $100 + call TitleScreenCopyTileMapToVRAM + call SaveScreenTilesToBuffer1 + ld a, $40 + ld [hWY], a + call LoadScreenTilesFromBuffer2 + ld a, vBGMap0 / $100 + call TitleScreenCopyTileMapToVRAM + ld b, SET_PAL_TITLE_SCREEN + call RunPaletteCommand + call GBPalNormal + ld a, %11100100 + ld [rOBP0], a + +; make pokemon logo bounce up and down + ld bc, hSCY ; background scroll Y + ld hl, .TitleScreenPokemonLogoYScrolls +.bouncePokemonLogoLoop + ld a, [hli] + and a + jr z, .finishedBouncingPokemonLogo + ld d, a + cp -3 + jr nz, .skipPlayingSound + ld a, SFX_INTRO_CRASH + call PlaySound +.skipPlayingSound + ld a, [hli] + ld e, a + call .ScrollTitleScreenPokemonLogo + jr .bouncePokemonLogoLoop + +.TitleScreenPokemonLogoYScrolls: +; Controls the bouncing effect of the Pokemon logo on the title screen + db -4,16 ; y scroll amount, number of times to scroll + db 3,4 + db -3,4 + db 2,2 + db -2,2 + db 1,2 + db -1,2 + db 0 ; terminate list with 0 + +.ScrollTitleScreenPokemonLogo: +; Scrolls the Pokemon logo on the title screen to create the bouncing effect +; Scrolls d pixels e times + call DelayFrame + ld a, [bc] ; background scroll Y + add d + ld [bc], a + dec e + jr nz, .ScrollTitleScreenPokemonLogo + ret + +.finishedBouncingPokemonLogo + call LoadScreenTilesFromBuffer1 + ld c, 36 + call DelayFrames + ld a, SFX_INTRO_WHOOSH + call PlaySound + +; scroll game version in from the right + call PrintGameVersionOnTitleScreen + ld a, SCREEN_HEIGHT_PIXELS + ld [hWY], a + ld d, 144 +.scrollTitleScreenGameVersionLoop + ld h, d + ld l, 64 + call ScrollTitleScreenGameVersion + ld h, 0 + ld l, 80 + call ScrollTitleScreenGameVersion + ld a, d + add 4 + ld d, a + and a + jr nz, .scrollTitleScreenGameVersionLoop + + ld a, vBGMap1 / $100 + call TitleScreenCopyTileMapToVRAM + call LoadScreenTilesFromBuffer2 + call PrintGameVersionOnTitleScreen + call Delay3 + call WaitForSoundToFinish + ld a, MUSIC_TITLE_SCREEN + ld [wNewSoundID], a + call PlaySound + xor a + ld [wUnusedCC5B], a + +; Keep scrolling in new mons indefinitely until the user performs input. +.awaitUserInterruptionLoop + ld c, 200 + call CheckForUserInterruption + jr c, .finishedWaiting + call TitleScreenScrollInMon + ld c, 1 + call CheckForUserInterruption + jr c, .finishedWaiting + callba TitleScreenAnimateBallIfStarterOut + call TitleScreenPickNewMon + jr .awaitUserInterruptionLoop + +.finishedWaiting + ld a, [wTitleMonSpecies] + call PlayCry + call WaitForSoundToFinish + call GBPalWhiteOutWithDelay3 + call ClearSprites + xor a + ld [hWY], a + inc a + ld [H_AUTOBGTRANSFERENABLED], a + call ClearScreen + ld a, vBGMap0 / $100 + call TitleScreenCopyTileMapToVRAM + ld a, vBGMap1 / $100 + call TitleScreenCopyTileMapToVRAM + call Delay3 + call LoadGBPal + ld a, [hJoyHeld] + ld b, a + and D_UP | SELECT | B_BUTTON + cp D_UP | SELECT | B_BUTTON + jp z, .doClearSaveDialogue + jp MainMenu + +.doClearSaveDialogue + jpba DoClearSaveDialogue + +TitleScreenPickNewMon: + ld a, vBGMap0 / $100 + call TitleScreenCopyTileMapToVRAM + +.loop +; Keep looping until a mon different from the current one is picked. + call Random + and $f + ld c, a + ld b, 0 + ld hl, TitleMons + add hl, bc + ld a, [hl] + ld hl, wTitleMonSpecies + +; Can't be the same as before. + cp [hl] + jr z, .loop + + ld [hl], a + call LoadTitleMonSprite + + ld a, $90 + ld [hWY], a + ld d, 1 ; scroll out + callba TitleScroll + ret + +TitleScreenScrollInMon: + ld d, 0 ; scroll in + callba TitleScroll + xor a + ld [hWY], a + ret + +ScrollTitleScreenGameVersion: +.wait + ld a, [rLY] + cp l + jr nz, .wait + + ld a, h + ld [rSCX], a + +.wait2 + ld a, [rLY] + cp h + jr z, .wait2 + ret + +DrawPlayerCharacter: + ld hl, PlayerCharacterTitleGraphics + ld de, vSprites + ld bc, PlayerCharacterTitleGraphicsEnd - PlayerCharacterTitleGraphics + ld a, BANK(PlayerCharacterTitleGraphics) + call FarCopyData2 + call ClearSprites + xor a + ld [wPlayerCharacterOAMTile], a + ld hl, wOAMBuffer + ld de, $605a + ld b, 7 +.loop + push de + ld c, 5 +.innerLoop + ld a, d + ld [hli], a ; Y + ld a, e + ld [hli], a ; X + add 8 + ld e, a + ld a, [wPlayerCharacterOAMTile] + ld [hli], a ; tile + inc a + ld [wPlayerCharacterOAMTile], a + inc hl + dec c + jr nz, .innerLoop + pop de + ld a, 8 + add d + ld d, a + dec b + jr nz, .loop + ret + +ClearBothBGMaps: + ld hl, vBGMap0 + ld bc, $400 * 2 + ld a, " " + jp FillMemory + +LoadTitleMonSprite: + ld [wcf91], a + ld [wd0b5], a + coord hl, 5, 10 + call GetMonHeader + jp LoadFrontSpriteByMonIndex + +TitleScreenCopyTileMapToVRAM: + ld [H_AUTOBGTRANSFERDEST + 1], a + jp Delay3 + +LoadCopyrightAndTextBoxTiles: + xor a + ld [hWY], a + call ClearScreen + call LoadTextBoxTilePatterns + +LoadCopyrightTiles: + ld de, NintendoCopyrightLogoGraphics + ld hl, vChars2 + $600 + lb bc, BANK(NintendoCopyrightLogoGraphics), (GamefreakLogoGraphicsEnd - NintendoCopyrightLogoGraphics) / $0f + call CopyVideoData + coord hl, 2, 7 + ld de, CopyrightTextString + jp PlaceString + +CopyrightTextString: + db $60,$61,$62,$63,$61,$62,$7C,$7F,$65,$66,$67,$68,$69,$6A ; ©1995-1999 Nintendo + next $60,$61,$62,$63,$61,$62,$7C,$7F,$6B,$6C,$6D,$6E,$6F,$70,$71,$72 ; ©1995-1999 Creatures inc. + next $60,$61,$62,$63,$61,$62,$7C,$7F,$73,$74,$75,$76,$77,$78,$79,$7A,$7B ; ©1995-1999 GAME FREAK inc. + db "@" + +INCLUDE "data/title_mons.asm" + +; prints version text (red, blue) +PrintGameVersionOnTitleScreen: + coord hl, 6, 8 + ld de, VersionOnTitleScreenText + jp PlaceString + +; these point to special tiles specifically loaded for that purpose and are not usual text +VersionOnTitleScreenText: +db $60,$61,$62,$63,$64,$65,$66,$67,$68,$69,"@" ; "Version Rouge" or "Version Bleue" + +NintenText: db "NINTEN@" +SonyText: db "SONY@" diff --git a/de/engine/town_map.asm b/de/engine/town_map.asm new file mode 100755 index 00000000..63825c0e --- /dev/null +++ b/de/engine/town_map.asm @@ -0,0 +1,619 @@ +DisplayTownMap: + call LoadTownMap + ld hl, wUpdateSpritesEnabled + ld a, [hl] + push af + ld [hl], $ff + push hl + ld a, $1 + ld [hJoy7], a + ld a, [wCurMap] + push af + ld b, $0 + call DrawPlayerOrBirdSprite ; player sprite + coord hl, 1, 0 + ld de, wcd6d + call PlaceString + ld hl, wOAMBuffer + ld de, wTileMapBackup + ld bc, $10 + call CopyData + ld hl, vSprites + $40 + ld de, TownMapCursor + lb bc, BANK(TownMapCursor), (TownMapCursorEnd - TownMapCursor) / $8 + call CopyVideoDataDouble + xor a + ld [wWhichTownMapLocation], a + pop af + jr .enterLoop + +.townMapLoop + coord hl, 0, 0 + lb bc, 1, 20 + call ClearScreenArea + ld hl, TownMapOrder + ld a, [wWhichTownMapLocation] + ld c, a + ld b, 0 + add hl, bc + ld a, [hl] +.enterLoop + ld de, wTownMapCoords + call LoadTownMapEntry + ld a, [de] + push hl + call TownMapCoordsToOAMCoords + ld a, $4 + ld [wOAMBaseTile], a + ld hl, wOAMBuffer + $10 + call WriteTownMapSpriteOAM ; town map cursor sprite + pop hl + ld de, wcd6d +.copyMapName + ld a, [hli] + ld [de], a + inc de + cp $50 + jr nz, .copyMapName + coord hl, 1, 0 + ld de, wcd6d + call PlaceString + ld hl, wOAMBuffer + $10 + ld de, wTileMapBackup + 16 + ld bc, $10 + call CopyData +.inputLoop + call TownMapSpriteBlinkingAnimation + call JoypadLowSensitivity + ld a, [hJoy5] + ld b, a + and A_BUTTON | B_BUTTON | D_UP | D_DOWN + jr z, .inputLoop + ld a, SFX_TINK + call PlaySound + bit 6, b + jr nz, .pressedUp + bit 7, b + jr nz, .pressedDown + xor a + ld [wTownMapSpriteBlinkingEnabled], a + ld [hJoy7], a + ld [wAnimCounter], a + call ExitTownMap + pop hl + pop af + ld [hl], a + ret +.pressedUp + ld a, [wWhichTownMapLocation] + inc a + cp TownMapOrderEnd - TownMapOrder ; number of list items + 1 + jr nz, .noOverflow + xor a +.noOverflow + ld [wWhichTownMapLocation], a + jp .townMapLoop +.pressedDown + ld a, [wWhichTownMapLocation] + dec a + cp -1 + jr nz, .noUnderflow + ld a, TownMapOrderEnd - TownMapOrder - 1 ; number of list items +.noUnderflow + ld [wWhichTownMapLocation], a + jp .townMapLoop + +INCLUDE "data/town_map_order.asm" + +TownMapCursor: + INCBIN "gfx/town_map_cursor.1bpp" +TownMapCursorEnd: + +LoadTownMap_Nest: + call LoadTownMap + ld hl, wUpdateSpritesEnabled + ld a, [hl] + push af + ld [hl], $ff + push hl + call DisplayWildLocations + call GetMonName + coord hl, 1, 0 + call PlaceString + ld h, b + ld l, c + ld de, MonsNestText + call PlaceString + call WaitForTextScrollButtonPress + call ExitTownMap + pop hl + pop af + ld [hl], a + ret + +MonsNestText: + db " FUNDORT@" + +LoadTownMap_Fly: + call ClearSprites + call LoadTownMap + call LoadPlayerSpriteGraphics + call LoadFontTilePatterns + ld de, BirdSprite + ld hl, vSprites + $40 + lb bc, BANK(BirdSprite), $c + call CopyVideoData + ld de, TownMapUpArrow + ld hl, vChars1 + $6d0 + lb bc, BANK(TownMapUpArrow), (TownMapUpArrowEnd - TownMapUpArrow) / $8 + call CopyVideoDataDouble + call BuildFlyLocationsList + ld hl, wUpdateSpritesEnabled + ld a, [hl] + push af + ld [hl], $ff + push hl + coord hl, 0, 0 + ld de, ToText + call PlaceString + ld a, [wCurMap] + ld b, $0 + call DrawPlayerOrBirdSprite + ld hl, wFlyLocationsList + coord de, 18, 0 +.townMapFlyLoop + ld a, " " + ld [de], a + push hl + push hl + coord hl, 3, 0 + lb bc, 1, 15 + call ClearScreenArea + pop hl + ld a, [hl] + ld b, $4 + call DrawPlayerOrBirdSprite ; draw bird sprite + coord hl, 3, 0 + ld de, wcd6d + call PlaceString + ld c, 15 + call DelayFrames + coord hl, 18, 0 + ld [hl], "▲" + coord hl, 19, 0 + ld [hl], "▼" + pop hl +.inputLoop + push hl + call DelayFrame + call JoypadLowSensitivity + ld a, [hJoy5] + ld b, a + pop hl + and A_BUTTON | B_BUTTON | D_UP | D_DOWN + jr z, .inputLoop + bit 0, b + jr nz, .pressedA + ld a, SFX_TINK + call PlaySound + bit 6, b + jr nz, .pressedUp + bit 7, b + jr nz, .pressedDown + jr .pressedB +.pressedA + ld a, SFX_HEAL_AILMENT + call PlaySound + ld a, [hl] + ld [wDestinationMap], a + ld hl, wd732 + set 3, [hl] + inc hl + set 7, [hl] +.pressedB + xor a + ld [wTownMapSpriteBlinkingEnabled], a + call GBPalWhiteOutWithDelay3 + pop hl + pop af + ld [hl], a + ret +.pressedUp + coord de, 18, 0 + inc hl + ld a, [hl] + cp $ff + jr z, .wrapToStartOfList + cp $fe + jr z, .pressedUp ; skip past unvisited towns + jp .townMapFlyLoop +.wrapToStartOfList + ld hl, wFlyLocationsList + jp .townMapFlyLoop +.pressedDown + coord de, 19, 0 + dec hl + ld a, [hl] + cp $ff + jr z, .wrapToEndOfList + cp $fe + jr z, .pressedDown ; skip past unvisited towns + jp .townMapFlyLoop +.wrapToEndOfList + ld hl, wFlyLocationsList + 11 + jr .pressedDown + +ToText: + db " ‘@" + +BuildFlyLocationsList: + ld hl, wFlyLocationsList - 1 + ld [hl], $ff + inc hl + ld a, [wTownVisitedFlag] + ld e, a + ld a, [wTownVisitedFlag + 1] + ld d, a + ld bc, SAFFRON_CITY + 1 +.loop + srl d + rr e + ld a, $fe ; store $fe if the town hasn't been visited + jr nc, .notVisited + ld a, b ; store the map number of the town if it has been visited +.notVisited + ld [hl], a + inc hl + inc b + dec c + jr nz, .loop + ld [hl], $ff + ret + +TownMapUpArrow: + INCBIN "gfx/up_arrow.1bpp" +TownMapUpArrowEnd: + +LoadTownMap: + call GBPalWhiteOutWithDelay3 + call ClearScreen + call UpdateSprites + coord hl, 0, 0 + ld b, $12 + ld c, $12 + call TextBoxBorder + call DisableLCD + ld hl, WorldMapTileGraphics + ld de, vChars2 + $600 + ld bc, WorldMapTileGraphicsEnd - WorldMapTileGraphics + ld a, BANK(WorldMapTileGraphics) + call FarCopyData2 + ld hl, MonNestIcon + ld de, vSprites + $40 + ld bc, MonNestIconEnd - MonNestIcon + ld a, BANK(MonNestIcon) + call FarCopyDataDouble + coord hl, 0, 0 + ld de, CompressedMap +.nextTile + ld a, [de] + and a + jr z, .done + ld b, a + and $f + ld c, a + ld a, b + swap a + and $f + add $60 +.writeRunLoop + ld [hli], a + dec c + jr nz, .writeRunLoop + inc de + jr .nextTile +.done + call EnableLCD + ld b, SET_PAL_TOWN_MAP + call RunPaletteCommand + call Delay3 + call GBPalNormal + xor a + ld [wAnimCounter], a + inc a + ld [wTownMapSpriteBlinkingEnabled], a + ret + +CompressedMap: +; you can decompress this file with the redrle program in the extras/ dir + INCBIN "gfx/town_map.rle" + +ExitTownMap: +; clear town map graphics data and load usual graphics data + xor a + ld [wTownMapSpriteBlinkingEnabled], a + call GBPalWhiteOut + call ClearScreen + call ClearSprites + call LoadPlayerSpriteGraphics + call LoadFontTilePatterns + call UpdateSprites + jp RunDefaultPaletteCommand + +DrawPlayerOrBirdSprite: +; a = map number +; b = OAM base tile + push af + ld a, b + ld [wOAMBaseTile], a + pop af + ld de, wTownMapCoords + call LoadTownMapEntry + ld a, [de] + push hl + call TownMapCoordsToOAMCoords + call WritePlayerOrBirdSpriteOAM + pop hl + ld de, wcd6d +.loop + ld a, [hli] + ld [de], a + inc de + cp "@" + jr nz, .loop + ld hl, wOAMBuffer + ld de, wTileMapBackup + ld bc, $a0 + jp CopyData + +DisplayWildLocations: + callba FindWildLocationsOfMon + call ZeroOutDuplicatesInList + ld hl, wOAMBuffer + ld de, wTownMapCoords +.loop + ld a, [de] + cp $ff + jr z, .exitLoop + and a + jr z, .nextEntry + push hl + call LoadTownMapEntry + pop hl + ld a, [de] + cp $19 ; Cerulean Cave's coordinates + jr z, .nextEntry ; skip Cerulean Cave + call TownMapCoordsToOAMCoords + ld a, $4 ; nest icon tile no. + ld [hli], a + xor a + ld [hli], a +.nextEntry + inc de + jr .loop +.exitLoop + ld a, l + and a ; were any OAM entries written? + jr nz, .drawPlayerSprite +; if no OAM entries were written, print area unknown text + coord hl, 1, 7 + ld b, 2 + ld c, 15 + call TextBoxBorder + coord hl, 2, 9 + ld de, AreaUnknownText + call PlaceString + jr .done +.drawPlayerSprite + ld a, [wCurMap] + ld b, $0 + call DrawPlayerOrBirdSprite +.done + ld hl, wOAMBuffer + ld de, wTileMapBackup + ld bc, $a0 + jp CopyData + +AreaUnknownText: + db " GEBIET UNB.@" + +TownMapCoordsToOAMCoords: +; in: lower nybble of a = x, upper nybble of a = y +; out: b and [hl] = (y * 8) + 24, c and [hl+1] = (x * 8) + 24 + push af + and $f0 + srl a + add 24 + ld b, a + ld [hli], a + pop af + and $f + swap a + srl a + add 24 + ld c, a + ld [hli], a + ret + +WritePlayerOrBirdSpriteOAM: + ld a, [wOAMBaseTile] + and a + ld hl, wOAMBuffer + $90 ; for player sprite + jr z, WriteTownMapSpriteOAM + ld hl, wOAMBuffer + $80 ; for bird sprite + +WriteTownMapSpriteOAM: + push hl + +; Subtract 4 from c (X coord) and 4 from b (Y coord). However, the carry from c +; is added to b, so the net result is that only 3 is subtracted from b. + lb hl, -4, -4 + add hl, bc + + ld b, h + ld c, l + pop hl + +WriteAsymmetricMonPartySpriteOAM: +; Writes 4 OAM blocks for a helix mon party sprite, since it does not have +; a vertical line of symmetry. + lb de, 2, 2 +.loop + push de + push bc +.innerLoop + ld a, b + ld [hli], a + ld a, c + ld [hli], a + ld a, [wOAMBaseTile] + ld [hli], a + inc a + ld [wOAMBaseTile], a + xor a + ld [hli], a + inc d + ld a, 8 + add c + ld c, a + dec e + jr nz, .innerLoop + pop bc + pop de + ld a, 8 + add b + ld b, a + dec d + jr nz, .loop + ret + +WriteSymmetricMonPartySpriteOAM: +; Writes 4 OAM blocks for a mon party sprite other than a helix. All the +; sprites other than the helix one have a vertical line of symmetry which allows +; the X-flip OAM bit to be used so that only 2 rather than 4 tile patterns are +; needed. + xor a + ld [wSymmetricSpriteOAMAttributes], a + lb de, 2, 2 +.loop + push de + push bc +.innerLoop + ld a, b + ld [hli], a ; Y + ld a, c + ld [hli], a ; X + ld a, [wOAMBaseTile] + ld [hli], a ; tile + ld a, [wSymmetricSpriteOAMAttributes] + ld [hli], a ; attributes + xor (1 << OAM_X_FLIP) + ld [wSymmetricSpriteOAMAttributes], a + inc d + ld a, 8 + add c + ld c, a + dec e + jr nz, .innerLoop + pop bc + pop de + push hl + ld hl, wOAMBaseTile + inc [hl] + inc [hl] + pop hl + ld a, 8 + add b + ld b, a + dec d + jr nz, .loop + ret + +ZeroOutDuplicatesInList: +; replace duplicate bytes in the list of wild pokemon locations with 0 + ld de, wBuffer +.loop + ld a, [de] + inc de + cp $ff + ret z + ld c, a + ld l, e + ld h, d +.zeroDuplicatesLoop + ld a, [hl] + cp $ff + jr z, .loop + cp c + jr nz, .skipZeroing + xor a + ld [hl], a +.skipZeroing + inc hl + jr .zeroDuplicatesLoop + +LoadTownMapEntry: +; in: a = map number +; out: lower nybble of [de] = x, upper nybble of [de] = y, hl = address of name + cp REDS_HOUSE_1F + jr c, .external + ld bc, 4 + ld hl, InternalMapEntries +.loop + cp [hl] + jr c, .foundEntry + add hl, bc + jr .loop +.foundEntry + inc hl + jr .readEntry +.external + ld hl, ExternalMapEntries + ld c, a + ld b, 0 + add hl, bc + add hl, bc + add hl, bc +.readEntry + ld a, [hli] + ld [de], a + ld a, [hli] + ld h, [hl] + ld l, a + ret + +INCLUDE "data/town_map_entries.asm" + +INCLUDE "text/map_names.asm" + +MonNestIcon: + INCBIN "gfx/mon_nest_icon.1bpp" +MonNestIconEnd: + +TownMapSpriteBlinkingAnimation: + ld a, [wAnimCounter] + inc a + cp 25 + jr z, .hideSprites + cp 50 + jr nz, .done +; show sprites when the counter reaches 50 + ld hl, wTileMapBackup + ld de, wOAMBuffer + ld bc, $90 + call CopyData + xor a + jr .done +.hideSprites + ld hl, wOAMBuffer + ld b, $24 + ld de, $4 +.hideSpritesLoop + ld [hl], $a0 + add hl, de + dec b + jr nz, .hideSpritesLoop + ld a, 25 +.done + ld [wAnimCounter], a + jp DelayFrame diff --git a/de/engine/trade.asm b/de/engine/trade.asm new file mode 100755 index 00000000..b168411f --- /dev/null +++ b/de/engine/trade.asm @@ -0,0 +1,853 @@ +InternalClockTradeAnim: +; Do the trading animation with the player's gameboy on the left. +; In-game trades and internally clocked link cable trades use this. + ld a, [wTradedPlayerMonSpecies] + ld [wLeftGBMonSpecies], a + ld a, [wTradedEnemyMonSpecies] + ld [wRightGBMonSpecies], a + ld de, InternalClockTradeFuncSequence + jr TradeAnimCommon + +ExternalClockTradeAnim: +; Do the trading animation with the player's gameboy on the right. +; Externally clocked link cable trades use this. + ld a, [wTradedEnemyMonSpecies] + ld [wLeftGBMonSpecies], a + ld a, [wTradedPlayerMonSpecies] + ld [wRightGBMonSpecies], a + ld de, ExternalClockTradeFuncSequence + +TradeAnimCommon: + ld a, [wOptions] + push af + ld a, [hSCY] + push af + ld a, [hSCX] + push af + xor a + ld [wOptions], a + ld [hSCY], a + ld [hSCX], a + push de +.loop + pop de + ld a, [de] + cp $ff + jr z, .done + inc de + push de + ld hl, TradeFuncPointerTable + add a + ld c, a + ld b, $0 + add hl, bc + ld a, [hli] + ld h, [hl] + ld l, a + ld de, .loop + push de + jp hl ; call trade func, which will return to the top of the loop +.done + pop af + ld [hSCX], a + pop af + ld [hSCY], a + pop af + ld [wOptions], a + ret + +addtradefunc: MACRO +\1TradeFunc:: + dw \1 + ENDM + +tradefunc: MACRO + db (\1TradeFunc - TradeFuncPointerTable) / 2 + ENDM + +; The functions in the sequences below are executed in order by TradeFuncCommon. +; They are from opposite perspectives. The external clock one makes use of +; Trade_SwapNames to swap the player and enemy names for some functions. + +InternalClockTradeFuncSequence: + tradefunc LoadTradingGFXAndMonNames + tradefunc Trade_ShowPlayerMon + tradefunc Trade_DrawOpenEndOfLinkCable + tradefunc Trade_AnimateBallEnteringLinkCable + tradefunc Trade_AnimLeftToRight + tradefunc Trade_Delay100 + tradefunc Trade_ShowClearedWindow + tradefunc PrintTradeWentToText + tradefunc PrintTradeForSendsText + tradefunc PrintTradeFarewellText + tradefunc Trade_AnimRightToLeft + tradefunc Trade_ShowClearedWindow + tradefunc Trade_DrawOpenEndOfLinkCable + tradefunc Trade_ShowEnemyMon + tradefunc Trade_Delay100 + tradefunc Trade_Cleanup + db $FF + +ExternalClockTradeFuncSequence: + tradefunc LoadTradingGFXAndMonNames + tradefunc Trade_ShowClearedWindow + tradefunc PrintTradeWillTradeText + tradefunc PrintTradeFarewellText + tradefunc Trade_SwapNames + tradefunc Trade_AnimLeftToRight + tradefunc Trade_SwapNames + tradefunc Trade_ShowClearedWindow + tradefunc Trade_DrawOpenEndOfLinkCable + tradefunc Trade_ShowEnemyMon + tradefunc Trade_SlideTextBoxOffScreen + tradefunc Trade_ShowPlayerMon + tradefunc Trade_DrawOpenEndOfLinkCable + tradefunc Trade_AnimateBallEnteringLinkCable + tradefunc Trade_SwapNames + tradefunc Trade_AnimRightToLeft + tradefunc Trade_SwapNames + tradefunc Trade_Delay100 + tradefunc Trade_ShowClearedWindow + tradefunc PrintTradeWentToText + tradefunc Trade_Cleanup + db $FF + +TradeFuncPointerTable: + addtradefunc LoadTradingGFXAndMonNames + addtradefunc Trade_ShowPlayerMon + addtradefunc Trade_DrawOpenEndOfLinkCable + addtradefunc Trade_AnimateBallEnteringLinkCable + addtradefunc Trade_ShowEnemyMon + addtradefunc Trade_AnimLeftToRight + addtradefunc Trade_AnimRightToLeft + addtradefunc Trade_Delay100 + addtradefunc Trade_ShowClearedWindow + addtradefunc PrintTradeWentToText + addtradefunc PrintTradeForSendsText + addtradefunc PrintTradeFarewellText + addtradefunc PrintTradeTakeCareText + addtradefunc PrintTradeWillTradeText + addtradefunc Trade_Cleanup + addtradefunc Trade_SlideTextBoxOffScreen + addtradefunc Trade_SwapNames + +Trade_Delay100: + ld c, 100 + jp DelayFrames + +Trade_CopyTileMapToVRAM: + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + call Delay3 + xor a + ld [H_AUTOBGTRANSFERENABLED], a + ret + +Trade_Delay80: + ld c, 80 + jp DelayFrames + +Trade_ClearTileMap: + coord hl, 0, 0 + ld bc, SCREEN_WIDTH * SCREEN_HEIGHT + ld a, " " + jp FillMemory + +LoadTradingGFXAndMonNames: + call Trade_ClearTileMap + call DisableLCD + ld hl, TradingAnimationGraphics + ld de, vChars2 + $310 + ld bc, TradingAnimationGraphicsEnd - TradingAnimationGraphics + ld a, BANK(TradingAnimationGraphics) + call FarCopyData2 + ld hl, TradingAnimationGraphics2 + ld de, vSprites + $7c0 + ld bc, TradingAnimationGraphics2End - TradingAnimationGraphics2 + ld a, BANK(TradingAnimationGraphics2) + call FarCopyData2 + ld hl, vBGMap0 + ld bc, $800 + ld a, " " + call FillMemory + call ClearSprites + ld a, $ff + ld [wUpdateSpritesEnabled], a + ld hl, wd730 + set 6, [hl] ; turn on instant text printing + ld a, [wOnSGB] + and a + ld a, $e4 ; non-SGB OBP0 + jr z, .next + ld a, $f0 ; SGB OBP0 +.next + ld [rOBP0], a + call EnableLCD + xor a + ld [H_AUTOBGTRANSFERENABLED], a + ld a, [wTradedPlayerMonSpecies] + ld [wd11e], a + call GetMonName + ld hl, wcd6d + ld de, wcf50 + ld bc, NAME_LENGTH + call CopyData + ld a, [wTradedEnemyMonSpecies] + ld [wd11e], a + jp GetMonName + +Trade_LoadMonPartySpriteGfx: + ld a, %11010000 + ld [rOBP1], a + jpba LoadMonPartySpriteGfx + +Trade_SwapNames: + ld hl, wPlayerName + ld de, wBuffer + ld bc, NAME_LENGTH + call CopyData + ld hl, wLinkEnemyTrainerName + ld de, wPlayerName + ld bc, NAME_LENGTH + call CopyData + ld hl, wBuffer + ld de, wLinkEnemyTrainerName + ld bc, NAME_LENGTH + jp CopyData + +Trade_Cleanup: + xor a + call LoadGBPal + ld hl, wd730 + res 6, [hl] ; turn off instant text printing + ret + +Trade_ShowPlayerMon: + ld a, %10101011 + ld [rLCDC], a + ld a, $50 + ld [hWY], a + ld a, $86 + ld [rWX], a + ld [hSCX], a + xor a + ld [H_AUTOBGTRANSFERENABLED], a + coord hl, 4, 0 + ld b, 6 + ld c, 10 + call TextBoxBorder + call Trade_PrintPlayerMonInfoText + ld b, vBGMap0 / $100 + call CopyScreenTileBufferToVRAM + call ClearScreen + ld a, [wTradedPlayerMonSpecies] + call Trade_LoadMonSprite + ld a, $7e +.slideScreenLoop + push af + call DelayFrame + pop af + ld [rWX], a + ld [hSCX], a + dec a + dec a + and a + jr nz, .slideScreenLoop + call Trade_Delay80 + ld a, TRADE_BALL_POOF_ANIM + call Trade_ShowAnimation + ld a, TRADE_BALL_DROP_ANIM + call Trade_ShowAnimation ; clears mon pic + ld a, [wTradedPlayerMonSpecies] + call PlayCry + xor a + ld [H_AUTOBGTRANSFERENABLED], a + ret + +Trade_DrawOpenEndOfLinkCable: + call Trade_ClearTileMap + ld b, vBGMap0 / $100 + call CopyScreenTileBufferToVRAM + ld b, SET_PAL_GENERIC + call RunPaletteCommand + +; This function call is pointless. It just copies blank tiles to VRAM that was +; already filled with blank tiles. + ld hl, vBGMap1 + $8c + call Trade_CopyCableTilesOffScreen + + ld a, $a0 + ld [hSCX], a + call DelayFrame + ld a, %10001011 + ld [rLCDC], a + coord hl, 6, 2 + ld b, $7 ; open end of link cable tile ID list index + call CopyTileIDsFromList_ZeroBaseTileID + call Trade_CopyTileMapToVRAM + ld a, SFX_HEAL_HP + call PlaySound + ld c, 20 +.loop + ld a, [hSCX] + add 4 + ld [hSCX], a + dec c + jr nz, .loop + ret + +Trade_AnimateBallEnteringLinkCable: + ld a, TRADE_BALL_SHAKE_ANIM + call Trade_ShowAnimation + ld c, 10 + call DelayFrames + ld a, %11100100 + ld [rOBP0], a + xor a + ld [wLinkCableAnimBulgeToggle], a + lb bc, $20, $60 +.moveBallInsideLinkCableLoop + push bc + xor a + ld de, Trade_BallInsideLinkCableOAM + call WriteOAMBlock + ld a, [wLinkCableAnimBulgeToggle] + xor $1 + ld [wLinkCableAnimBulgeToggle], a + add $7e + ld hl, wOAMBuffer + $02 + ld de, 4 + ld c, e +.cycleLinkCableBulgeTile + ld [hl], a + add hl, de + dec c + jr nz, .cycleLinkCableBulgeTile + call Delay3 + pop bc + ld a, c + add $4 + ld c, a + cp $a0 + jr nc, .ballSpriteReachedEdgeOfScreen + ld a, SFX_TINK + call PlaySound + jr .moveBallInsideLinkCableLoop +.ballSpriteReachedEdgeOfScreen + call ClearSprites + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + call ClearScreen + ld b, $98 + call CopyScreenTileBufferToVRAM + call Delay3 + xor a + ld [H_AUTOBGTRANSFERENABLED], a + ret + +Trade_BallInsideLinkCableOAM: + db $7E,$00,$7E,$20 + db $7E,$40,$7E,$60 + +Trade_ShowEnemyMon: + ld a, TRADE_BALL_TILT_ANIM + call Trade_ShowAnimation + call Trade_ShowClearedWindow + coord hl, 4, 10 + ld b, 6 + ld c, 10 + call TextBoxBorder + call Trade_PrintEnemyMonInfoText + call Trade_CopyTileMapToVRAM + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + ld a, [wTradedEnemyMonSpecies] + call Trade_LoadMonSprite + ld a, TRADE_BALL_POOF_ANIM + call Trade_ShowAnimation + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + ld a, [wTradedEnemyMonSpecies] + call PlayCry + call Trade_Delay100 + coord hl, 4, 10 + lb bc, 8, 12 + call ClearScreenArea + jp PrintTradeTakeCareText + +Trade_AnimLeftToRight: +; Animates the mon moving from the left GB to the right one. + call Trade_InitGameboyTransferGfx + ld a, $1 + ld [wTradedMonMovingRight], a + ld a, %11100100 + ld [rOBP0], a + ld a, $54 + ld [wBaseCoordX], a + ld a, $1c + ld [wBaseCoordY], a + ld a, [wLeftGBMonSpecies] + ld [wMonPartySpriteSpecies], a + call Trade_WriteCircledMonOAM + call Trade_DrawLeftGameboy + call Trade_CopyTileMapToVRAM + call Trade_DrawCableAcrossScreen + ld hl, vBGMap1 + $8c + call Trade_CopyCableTilesOffScreen + ld b, $6 + call Trade_AnimMonMoveHorizontal + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + call Trade_DrawCableAcrossScreen + ld b, $4 + call Trade_AnimMonMoveHorizontal + call Trade_DrawRightGameboy + ld b, $6 + call Trade_AnimMonMoveHorizontal + xor a + ld [H_AUTOBGTRANSFERENABLED], a + call Trade_AnimMonMoveVertical + jp ClearSprites + +Trade_AnimRightToLeft: +; Animates the mon moving from the right GB to the left one. + call Trade_InitGameboyTransferGfx + xor a + ld [wTradedMonMovingRight], a + ld a, $64 + ld [wBaseCoordX], a + ld a, $44 + ld [wBaseCoordY], a + ld a, [wRightGBMonSpecies] + ld [wMonPartySpriteSpecies], a + call Trade_WriteCircledMonOAM + call Trade_DrawRightGameboy + call Trade_CopyTileMapToVRAM + call Trade_DrawCableAcrossScreen + ld hl, vBGMap1 + $94 + call Trade_CopyCableTilesOffScreen + call Trade_AnimMonMoveVertical + ld b, $6 + call Trade_AnimMonMoveHorizontal + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + call Trade_DrawCableAcrossScreen + ld b, $4 + call Trade_AnimMonMoveHorizontal + call Trade_DrawLeftGameboy + ld b, $6 + call Trade_AnimMonMoveHorizontal + xor a + ld [H_AUTOBGTRANSFERENABLED], a + jp ClearSprites + +Trade_InitGameboyTransferGfx: +; Initialises the graphics for showing a mon moving between gameboys. + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + call ClearScreen + xor a + ld [H_AUTOBGTRANSFERENABLED], a + call Trade_LoadMonPartySpriteGfx + call DelayFrame + ld a, %10101011 + ld [rLCDC], a + xor a + ld [hSCX], a + ld a, $90 + ld [hWY], a + ret + +Trade_DrawLeftGameboy: + call Trade_ClearTileMap + +; draw link cable + coord hl, 11, 4 + ld a, $5d + ld [hli], a + ld a, $5e + ld c, 8 +.loop + ld [hli], a + dec c + jr nz, .loop + +; draw gameboy pic + coord hl, 5, 3 + ld b, $6 + call CopyTileIDsFromList_ZeroBaseTileID + +; draw text box with player name below gameboy pic + coord hl, 4, 12 + ld b, 2 + ld c, 7 + call TextBoxBorder + coord hl, 5, 14 + ld de, wPlayerName + call PlaceString + + jp DelayFrame + +Trade_DrawRightGameboy: + call Trade_ClearTileMap + +; draw horizontal segment of link cable + coord hl, 0, 4 + ld a, $5e + ld c, $e +.loop + ld [hli], a + dec c + jr nz, .loop + +; draw vertical segment of link cable + ld a, $5f + ld [hl], a + ld de, SCREEN_WIDTH + add hl, de + ld a, $61 + ld [hl], a + add hl, de + ld [hl], a + add hl, de + ld [hl], a + add hl, de + ld [hl], a + add hl, de + ld a, $60 + ld [hld], a + ld a, $5d + ld [hl], a + +; draw gameboy pic + coord hl, 7, 8 + ld b, $6 + call CopyTileIDsFromList_ZeroBaseTileID + +; draw text box with enemy name above link cable + coord hl, 6, 0 + ld b, 2 + ld c, 7 + call TextBoxBorder + coord hl, 7, 2 + ld de, wLinkEnemyTrainerName + call PlaceString + + jp DelayFrame + +Trade_DrawCableAcrossScreen: +; Draws the link cable across the screen. + call Trade_ClearTileMap + coord hl, 0, 4 + ld a, $5e + ld c, SCREEN_WIDTH +.loop + ld [hli], a + dec c + jr nz, .loop + ret + +Trade_CopyCableTilesOffScreen: +; This is used to copy the link cable tiles off screen so that the cable +; continues when the screen is scrolled. + push hl + coord hl, 0, 4 + call CopyToRedrawRowOrColumnSrcTiles + pop hl + ld a, h + ld [hRedrawRowOrColumnDest + 1], a + ld a, l + ld [hRedrawRowOrColumnDest], a + ld a, REDRAW_ROW + ld [hRedrawRowOrColumnMode], a + ld c, 10 + jp DelayFrames + +Trade_AnimMonMoveHorizontal: +; Animates the mon going through the link cable horizontally over a distance of +; b 16-pixel units. + ld a, [wTradedMonMovingRight] + ld e, a + ld d, $8 +.scrollLoop + ld a, e + dec a + jr z, .movingRight +; moving left + ld a, [hSCX] + sub $2 + jr .next +.movingRight + ld a, [hSCX] + add $2 +.next + ld [hSCX], a + call DelayFrame + dec d + jr nz, .scrollLoop + call Trade_AnimCircledMon + dec b + jr nz, Trade_AnimMonMoveHorizontal + ret + +Trade_AnimCircledMon: +; Cycles between the two animation frames of the mon party sprite, cycles +; between a circle and an oval around the mon sprite, and makes the cable flash. + push de + push bc + push hl + ld a, [rBGP] + xor $3c ; make link cable flash + ld [rBGP], a + ld hl, wOAMBuffer + $02 + ld de, $4 + ld c, $14 +.loop + ld a, [hl] + xor $40 + ld [hl], a + add hl, de + dec c + jr nz, .loop + pop hl + pop bc + pop de + ret + +Trade_WriteCircledMonOAM: + callba WriteMonPartySpriteOAMBySpecies + call Trade_WriteCircleOAM + +Trade_AddOffsetsToOAMCoords: + ld hl, wOAMBuffer + ld c, $14 +.loop + ld a, [wBaseCoordY] + add [hl] + ld [hli], a + ld a, [wBaseCoordX] + add [hl] + ld [hli], a + inc hl + inc hl + dec c + jr nz, .loop + ret + +Trade_AnimMonMoveVertical: +; Animates the mon going through the link cable vertically as well as +; horizontally for a bit. The last bit of horizontal movement (when moving +; right) or the first bit of horizontal movement (when moving left) are done +; here instead of Trade_AnimMonMoveHorizontal because this function moves the +; sprite itself rather than scrolling the screen around the sprite. Moving the +; sprite itself is necessary because the vertical segment of the link cable is +; to the right of the screen position that the mon sprite has when +; Trade_AnimMonMoveHorizontal is executing. + ld a, [wTradedMonMovingRight] + and a + jr z, .movingLeft +; moving right + lb bc, 4, 0 ; move right + call .doAnim + lb bc, 0, 10 ; move down + jr .doAnim +.movingLeft + lb bc, 0, -10 ; move up + call .doAnim + lb bc, -4, 0 ; move left +.doAnim + ld a, b + ld [wBaseCoordX], a + ld a, c + ld [wBaseCoordY], a + ld d, $4 +.loop + call Trade_AddOffsetsToOAMCoords + call Trade_AnimCircledMon + ld c, 8 + call DelayFrames + dec d + jr nz, .loop + ret + +Trade_WriteCircleOAM: +; Writes the OAM blocks for the circle around the traded mon as it passes +; the link cable. + ld hl, Trade_CircleOAMPointers + ld c, $4 + xor a +.loop + push bc + ld e, [hl] + inc hl + ld d, [hl] + inc hl + ld c, [hl] + inc hl + ld b, [hl] + inc hl + push hl + inc a + push af + call WriteOAMBlock + pop af + pop hl + pop bc + dec c + jr nz, .loop + ret + +Trade_CircleOAMPointers: + dw Trade_CircleOAM0 + db $08,$08 + dw Trade_CircleOAM1 + db $18,$08 + dw Trade_CircleOAM2 + db $08,$18 + dw Trade_CircleOAM3 + db $18,$18 + +Trade_CircleOAM0: + db $38,$10,$39,$10 + db $3A,$10,$3B,$10 + +Trade_CircleOAM1: + db $39,$30,$38,$30 + db $3B,$30,$3A,$30 + +Trade_CircleOAM2: + db $3A,$50,$3B,$50 + db $38,$50,$39,$50 + +Trade_CircleOAM3: + db $3B,$70,$3A,$70 + db $39,$70,$38,$70 + +; a = species +Trade_LoadMonSprite: + ld [wcf91], a + ld [wd0b5], a + ld [wWholeScreenPaletteMonSpecies], a + ld b, SET_PAL_POKEMON_WHOLE_SCREEN + ld c, 0 + call RunPaletteCommand + ld a, [H_AUTOBGTRANSFERENABLED] + xor $1 + ld [H_AUTOBGTRANSFERENABLED], a + call GetMonHeader + coord hl, 7, 2 + call LoadFlippedFrontSpriteByMonIndex + ld c, 10 + jp DelayFrames + +Trade_ShowClearedWindow: +; clears the window and covers the BG entirely with the window + ld a, $1 + ld [H_AUTOBGTRANSFERENABLED], a + call ClearScreen + ld a, %11100011 + ld [rLCDC], a + ld a, $7 + ld [rWX], a + xor a + ld [hWY], a + ld a, $90 + ld [hSCX], a + ret + +Trade_SlideTextBoxOffScreen: +; Slides the window right until it's off screen. The window usually just has +; a text box at the bottom when this is called. However, when this is called +; after Trade_ShowEnemyMon in the external clock sequence, there is a mon pic +; above the text box and it is also scrolled off the screen. + ld c, 50 + call DelayFrames +.loop + call DelayFrame + ld a, [rWX] + inc a + inc a + ld [rWX], a + cp $a1 + jr nz, .loop + call Trade_ClearTileMap + ld c, 10 + call DelayFrames + ld a, $7 + ld [rWX], a + ret + +PrintTradeWentToText: + ld hl, TradeWentToText + call PrintText + ld c, 200 + call DelayFrames + jp Trade_SlideTextBoxOffScreen + +TradeWentToText: + TX_FAR _TradeWentToText + db "@" + +PrintTradeForSendsText: + ld hl, TradeForText + call PrintText + call Trade_Delay80 + ld hl, TradeSendsText + call PrintText + jp Trade_Delay80 + +TradeForText: + TX_FAR _TradeForText + db "@" + +TradeSendsText: + TX_FAR _TradeSendsText + db "@" + +PrintTradeFarewellText: + ld hl, TradeWavesFarewellText + call PrintText + call Trade_Delay80 + ld hl, TradeTransferredText + call PrintText + call Trade_Delay80 + jp Trade_SlideTextBoxOffScreen + +TradeWavesFarewellText: + TX_FAR _TradeWavesFarewellText + db "@" + +TradeTransferredText: + TX_FAR _TradeTransferredText + db "@" + +PrintTradeTakeCareText: + ld hl, TradeTakeCareText + call PrintText + jp Trade_Delay80 + +TradeTakeCareText: + TX_FAR _TradeTakeCareText + db "@" + +PrintTradeWillTradeText: + ld hl, TradeWillTradeText + call PrintText + call Trade_Delay80 + ld hl, TradeforText + call PrintText + jp Trade_Delay80 + +TradeWillTradeText: + TX_FAR _TradeWillTradeText + db "@" + +TradeforText: + TX_FAR _TradeforText + db "@" + +Trade_ShowAnimation: + ld [wAnimationID], a + xor a + ld [wAnimationType], a + predef_jump MoveAnimation |