summaryrefslogtreecommitdiff
path: root/de/engine
diff options
context:
space:
mode:
Diffstat (limited to 'de/engine')
-rwxr-xr-xde/engine/HoF_room_pc.asm270
-rwxr-xr-xde/engine/battle/core.asm8720
-rwxr-xr-xde/engine/battle/end_of_battle.asm91
-rw-r--r--de/engine/battle/link_battle_versus_text.asm23
-rw-r--r--de/engine/battle/save_trainer_name.asm112
-rwxr-xr-xde/engine/cable_club.asm977
-rwxr-xr-xde/engine/clear_save.asm23
-rwxr-xr-xde/engine/evolve_trade.asm45
-rwxr-xr-xde/engine/hall_of_fame.asm288
-rwxr-xr-xde/engine/hidden_object_functions17.asm475
-rwxr-xr-xde/engine/hidden_object_functions7.asm467
-rwxr-xr-xde/engine/items/items.asm2989
-rwxr-xr-xde/engine/learn_move.asm226
-rw-r--r--de/engine/menu/bills_pc.asm554
-rwxr-xr-xde/engine/menu/diploma.asm112
-rw-r--r--de/engine/menu/draw_start_menu.asm89
-rwxr-xr-xde/engine/menu/league_pc.asm120
-rwxr-xr-xde/engine/menu/main_menu.asm712
-rwxr-xr-xde/engine/menu/naming_screen.asm512
-rwxr-xr-xde/engine/menu/party_menu.asm325
-rwxr-xr-xde/engine/menu/players_pc.asm303
-rwxr-xr-xde/engine/menu/pokedex.asm666
-rwxr-xr-xde/engine/menu/prize_menu.asm306
-rwxr-xr-xde/engine/menu/start_sub_menus.asm854
-rwxr-xr-xde/engine/menu/status_screen.asm491
-rw-r--r--de/engine/menu/text_box.asm740
-rwxr-xr-xde/engine/menu/vending_machine.asm139
-rwxr-xr-xde/engine/oak_speech2.asm272
-rw-r--r--de/engine/overworld/movement.asm893
-rwxr-xr-xde/engine/overworld/pokemart.asm272
-rw-r--r--de/engine/print_waiting_text.asm20
-rwxr-xr-xde/engine/save.asm708
-rwxr-xr-xde/engine/slot_machine.asm892
-rwxr-xr-xde/engine/status_ailments.asm46
-rwxr-xr-xde/engine/titlescreen.asm398
-rwxr-xr-xde/engine/town_map.asm619
-rwxr-xr-xde/engine/trade.asm853
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