summaryrefslogtreecommitdiff
path: root/engine/menus
diff options
context:
space:
mode:
Diffstat (limited to 'engine/menus')
-rw-r--r--engine/menus/display_text_id_init.asm76
-rw-r--r--engine/menus/draw_badges.asm120
-rw-r--r--engine/menus/draw_start_menu.asm87
-rwxr-xr-xengine/menus/league_pc.asm119
-rw-r--r--engine/menus/link_menu.asm910
-rwxr-xr-xengine/menus/main_menu.asm300
-rwxr-xr-xengine/menus/naming_screen.asm508
-rwxr-xr-xengine/menus/oaks_pc.asm28
-rw-r--r--engine/menus/options.asm443
-rwxr-xr-xengine/menus/party_menu.asm314
-rwxr-xr-xengine/menus/pc.asm141
-rwxr-xr-xengine/menus/players_pc.asm302
-rwxr-xr-xengine/menus/pokedex.asm745
-rwxr-xr-xengine/menus/save.asm682
-rwxr-xr-xengine/menus/start_sub_menus.asm825
-rw-r--r--engine/menus/swap_items.asm149
-rw-r--r--engine/menus/text_box.asm533
17 files changed, 6282 insertions, 0 deletions
diff --git a/engine/menus/display_text_id_init.asm b/engine/menus/display_text_id_init.asm
new file mode 100644
index 00000000..17f290b4
--- /dev/null
+++ b/engine/menus/display_text_id_init.asm
@@ -0,0 +1,76 @@
+; function that performs initialization for DisplayTextID
+DisplayTextIDInit::
+ xor a
+ ld [wListMenuID], a
+ ld a, [wAutoTextBoxDrawingControl]
+ bit 0, a
+ jr nz, .skipDrawingTextBoxBorder
+ ldh a, [hSpriteIndexOrTextID] ; text ID (or sprite ID)
+ and a
+ jr nz, .notStartMenu
+; if text ID is 0 (i.e. the start menu)
+; Note that the start menu text border is also drawn in the function directly
+; below this, so this seems unnecessary.
+ CheckEvent EVENT_GOT_POKEDEX
+; start menu with pokedex
+ hlcoord 10, 0
+ lb bc, 14, 8
+ jr nz, .drawTextBoxBorder
+; start menu without pokedex
+ hlcoord 10, 0
+ lb bc, 12, 8
+ jr .drawTextBoxBorder
+; if text ID is not 0 (i.e. not the start menu) then do a standard dialogue text box
+.notStartMenu
+ hlcoord 0, 12
+ lb bc, 4, 18
+.drawTextBoxBorder
+ call TextBoxBorder
+.skipDrawingTextBoxBorder
+ ld hl, wFontLoaded
+ set 0, [hl]
+ ld hl, wFlags_0xcd60
+ bit 4, [hl]
+ res 4, [hl]
+ jr nz, .skipMovingSprites
+ call UpdateSprites
+.skipMovingSprites
+; loop to copy [x#SPRITESTATEDATA1_FACINGDIRECTION] to
+; [x#SPRITESTATEDATA2_ORIGFACINGDIRECTION] for each non-player sprite
+; this is done because when you talk to an NPC, they turn to look your way
+; the original direction they were facing must be restored after the dialogue is over
+ ld hl, wSprite01StateData1FacingDirection
+ ld c, $0f
+ ld de, $10
+.spriteFacingDirectionCopyLoop
+ ld a, [hl] ; x#SPRITESTATEDATA1_FACINGDIRECTION
+ inc h
+ ld [hl], a ; [x#SPRITESTATEDATA2_ORIGFACINGDIRECTION]
+ dec h
+ add hl, de
+ dec c
+ jr nz, .spriteFacingDirectionCopyLoop
+; loop to force all the sprites in the middle of animation to stand still
+; (so that they don't like they're frozen mid-step during the dialogue)
+ ld hl, wSpritePlayerStateData1ImageIndex
+ ld de, $10
+ ld c, e
+.spriteStandStillLoop
+ ld a, [hl]
+ cp $ff ; is the sprite visible?
+ jr z, .nextSprite
+; if it is visible
+ and $fc
+ ld [hl], a
+.nextSprite
+ add hl, de
+ dec c
+ jr nz, .spriteStandStillLoop
+ ld b, $9c ; window background address
+ call CopyScreenTileBufferToVRAM ; transfer background in WRAM to VRAM
+ xor a
+ ldh [hWY], a ; put the window on the screen
+ call LoadFontTilePatterns
+ ld a, $01
+ ldh [hAutoBGTransferEnabled], a ; enable continuous WRAM to VRAM transfer each V-blank
+ ret
diff --git a/engine/menus/draw_badges.asm b/engine/menus/draw_badges.asm
new file mode 100644
index 00000000..0bb433cc
--- /dev/null
+++ b/engine/menus/draw_badges.asm
@@ -0,0 +1,120 @@
+DrawBadges:
+; Draw 4x2 gym leader faces, with the faces replaced by
+; badges if they are owned. Used in the player status screen.
+
+; In Japanese versions, names are displayed above faces.
+; Instead of removing relevant code, the name graphics were erased.
+
+; Tile ids for face/badge graphics.
+ ld de, wBadgeOrFaceTiles
+ ld hl, .FaceBadgeTiles
+ ld bc, NUM_BADGES
+ call CopyData
+
+; Booleans for each badge.
+ ld hl, wTempObtainedBadgesBooleans
+ ld bc, NUM_BADGES
+ xor a
+ call FillMemory
+
+; Alter these based on owned badges.
+ ld de, wTempObtainedBadgesBooleans
+ ld hl, wBadgeOrFaceTiles
+ ld a, [wObtainedBadges]
+ ld b, a
+ ld c, NUM_BADGES
+.CheckBadge
+ srl b
+ jr nc, .NextBadge
+ ld a, [hl]
+ add 4 ; Badge graphics are after each face
+ ld [hl], a
+ ld a, 1
+ ld [de], a
+.NextBadge
+ inc hl
+ inc de
+ dec c
+ jr nz, .CheckBadge
+
+; Draw two rows of badges.
+ ld hl, wBadgeNumberTile
+ ld a, $d8 ; [1]
+ ld [hli], a
+ ld [hl], $60 ; First name
+
+ hlcoord 2, 11
+ ld de, wTempObtainedBadgesBooleans
+ call .DrawBadgeRow
+
+ hlcoord 2, 14
+ ld de, wTempObtainedBadgesBooleans + 4
+; call .DrawBadgeRow
+; ret
+
+.DrawBadgeRow
+; Draw 4 badges.
+
+ ld c, 4
+.DrawBadge
+ push de
+ push hl
+
+; Badge no.
+ ld a, [wBadgeNumberTile]
+ ld [hli], a
+ inc a
+ ld [wBadgeNumberTile], a
+
+; Names aren't printed if the badge is owned.
+ ld a, [de]
+ and a
+ ld a, [wBadgeNameTile]
+ jr nz, .SkipName
+ call .PlaceTiles
+ jr .PlaceBadge
+
+.SkipName
+ inc a
+ inc a
+ inc hl
+
+.PlaceBadge
+ ld [wBadgeNameTile], a
+ ld de, SCREEN_WIDTH - 1
+ add hl, de
+ ld a, [wBadgeOrFaceTiles]
+ call .PlaceTiles
+ add hl, de
+ call .PlaceTiles
+
+; Shift badge array back one byte.
+ push bc
+ ld hl, wBadgeOrFaceTiles + 1
+ ld de, wBadgeOrFaceTiles
+ ld bc, NUM_BADGES
+ call CopyData
+ pop bc
+
+ pop hl
+ ld de, 4
+ add hl, de
+
+ pop de
+ inc de
+ dec c
+ jr nz, .DrawBadge
+ ret
+
+.PlaceTiles
+ ld [hli], a
+ inc a
+ ld [hl], a
+ inc a
+ ret
+
+.FaceBadgeTiles
+ db $20, $28, $30, $38, $40, $48, $50, $58
+
+GymLeaderFaceAndBadgeTileGraphics:
+ INCBIN "gfx/trainer_card/badges.2bpp"
diff --git a/engine/menus/draw_start_menu.asm b/engine/menus/draw_start_menu.asm
new file mode 100644
index 00000000..7ca75ecb
--- /dev/null
+++ b/engine/menus/draw_start_menu.asm
@@ -0,0 +1,87 @@
+; function that displays the start menu
+DrawStartMenu::
+ CheckEvent EVENT_GOT_POKEDEX
+; menu with pokedex
+ hlcoord 10, 0
+ lb bc, 14, 8
+ jr nz, .drawTextBoxBorder
+; shorter menu if the player doesn't have the pokedex
+ hlcoord 10, 0
+ lb bc, 12, 8
+.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
+ hlcoord 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 "POKéDEX@"
+
+StartMenuPokemonText:
+ db "#MON@"
+
+StartMenuItemText:
+ db "ITEM@"
+
+StartMenuSaveText:
+ db "SAVE@"
+
+StartMenuResetText:
+ db "RESET@"
+
+StartMenuExitText:
+ db "EXIT@"
+
+StartMenuOptionText:
+ db "OPTION@"
+
+PrintStartMenuItem:
+ push hl
+ call PlaceString
+ pop hl
+ ld de, SCREEN_WIDTH * 2
+ add hl, de
+ ret
diff --git a/engine/menus/league_pc.asm b/engine/menus/league_pc.asm
new file mode 100755
index 00000000..5551fd0b
--- /dev/null
+++ b/engine/menus/league_pc.asm
@@ -0,0 +1,119 @@
+PKMNLeaguePC:
+ ld hl, AccessedHoFPCText
+ call PrintText
+ ld hl, wd730
+ set 6, [hl]
+ push hl
+ ld a, [wUpdateSpritesEnabled]
+ push af
+ ldh a, [hTilesetType]
+ push af
+ xor a
+ ldh [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
+ farcall LoadHallOfFameTeams
+ call LeaguePCShowTeam
+ pop bc
+ jr c, .doneShowingTeams
+ ld hl, wHoFTeamIndex2
+ inc [hl]
+ ld a, [hl]
+ cp b
+ jr nz, .loop
+.doneShowingTeams
+ pop af
+ ldh [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
+ ldh 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
+ hlcoord 12, 5
+ call GetMonHeader
+ call LoadFrontSpriteByMonIndex
+ call GBPalNormal
+ hlcoord 0, 13
+ lb bc, 2, 18
+ call TextBoxBorder
+ hlcoord 1, 15
+ ld de, HallOfFameNoText
+ call PlaceString
+ hlcoord 16, 15
+ ld de, wHoFTeamNo
+ lb bc, 1, 3
+ call PrintNumber
+ farjp Func_7033f
+
+HallOfFameNoText:
+ db "HALL OF FAME No @"
+
+AccessedHoFPCText:
+ text_far _AccessedHoFPCText
+ text_end
diff --git a/engine/menus/link_menu.asm b/engine/menus/link_menu.asm
new file mode 100644
index 00000000..2f2db573
--- /dev/null
+++ b/engine/menus/link_menu.asm
@@ -0,0 +1,910 @@
+Func_f531b::
+ ld c, $14
+ call DelayFrames
+ ld a, $1
+ ld [wBuffer], a
+ xor a
+ ld [wUnknownSerialFlag_d499], a
+ coord hl, 0, 0
+ lb bc, 4, 5
+ call TextBoxBorder
+ ld de, Text_f5791
+ coord hl, 1, 2
+ call PlaceString
+ coord hl, 8, 0
+ lb bc, 8, 10
+ call TextBoxBorder
+ coord hl, 10, 2
+ ld de, Text_f579c
+ call PlaceString
+ coord hl, 0, 10
+ lb bc, 6, 18
+ call TextBoxBorder
+ call UpdateSprites
+ xor a
+ ld [wUnusedCD37], a
+ ld [wd72d], a
+ ld [wd11e], a
+ ld hl, wTopMenuItemY
+ ld a, $2
+ ld [hli], a
+ ld a, $9
+ ld [hli], a
+ xor a
+ ld [hli], a
+ inc hl
+ ld a, $3
+ ld [hli], a
+ ld a, $3
+ ld [hli], a
+ xor a
+ ld [hl], a
+.asm_f5377
+ call Func_f56bd
+ call HandleMenuInput
+ and $3
+ add a
+ add a
+ ld b, a
+ ld a, [wCurrentMenuItem]
+ cp $3
+ jr nz, .asm_f5390
+ bit 2, b
+ jr z, .asm_f5390
+ dec a
+ ld b, $8
+.asm_f5390
+ add b
+ add $c0
+ ld [wLinkMenuSelectionSendBuffer], a
+ ld [wLinkMenuSelectionSendBuffer+1], a
+.asm_f5399
+ ld hl, wLinkMenuSelectionSendBuffer
+ ld a, [hl]
+ ldh [hSerialSendData], a
+ call Serial_ExchangeByte
+ push af
+ ld hl, wLinkMenuSelectionSendBuffer
+ ld a, [hl]
+ ldh [hSerialSendData], a
+ call Serial_ExchangeByte
+ pop bc
+ cp b
+ jr nz, .asm_f5399
+ and $f0
+ cp $c0
+ jr nz, .asm_f5399
+ ld a, b
+ and $c
+ jr nz, .asm_f53c4
+ ld a, [wLinkMenuSelectionSendBuffer]
+ and $c
+ jr z, .asm_f5377
+ jr .asm_f53df
+.asm_f53c4
+ ld a, [wLinkMenuSelectionSendBuffer]
+ and $c
+ jr z, .asm_f53d1
+ ldh a, [hSerialConnectionStatus]
+ cp $2
+ jr z, .asm_f53df
+.asm_f53d1
+ ld a, $1
+ ld [wd11e], a
+ ld a, b
+ ld [wLinkMenuSelectionSendBuffer], a
+ and $3
+ ld [wCurrentMenuItem], a
+.asm_f53df
+ call DelayFrame
+ call DelayFrame
+ ld hl, wLinkMenuSelectionSendBuffer
+ ld a, [hl]
+ ldh [hSerialSendData], a
+ call Serial_ExchangeByte
+ call Serial_ExchangeByte
+ ld b, $14
+.loop
+ call DelayFrame
+ call Serial_SendZeroByte
+ dec b
+ jr nz, .loop
+ ld b, " "
+ ld c, " "
+ ld d, " "
+ ld e, "▷"
+ ld a, [wLinkMenuSelectionSendBuffer]
+ bit 3, a
+ jr nz, .asm_f541a
+ ld b, e
+ ld e, c
+ ld a, [wCurrentMenuItem]
+ and a
+ jr z, .asm_f541a
+ ld c, b
+ ld b, d
+ dec a
+ jr z, .asm_f541a
+ ld d, c
+ ld c, b
+.asm_f541a
+ ld a, b
+ ldcoord_a 9, 2
+ ld a, c
+ ldcoord_a 9, 4
+ ld a, d
+ ldcoord_a 9, 6
+ ld a, e
+ ldcoord_a 9, 8
+ ld c, 40
+ call DelayFrames
+ ld a, [wLinkMenuSelectionSendBuffer]
+ bit 3, a
+ jr nz, asm_f547f
+ ld a, [wCurrentMenuItem]
+ cp $3
+ jr z, asm_f547f
+ inc a
+ ld [wUnknownSerialFlag_d499], a
+ ld a, [wCurrentMenuItem]
+ ld hl, PointerTable_f5488
+ ld c, a
+ ld b, $0
+ add hl, bc
+ add hl, bc
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld de, .returnaddress
+ push de
+ jp hl
+.returnaddress
+ ld [wLinkMenuSelectionSendBuffer], a
+ xor a
+ ld [wUnknownSerialCounter], a
+ ld [wUnknownSerialCounter+1], a
+ call Serial_SyncAndExchangeNybble
+ ld a, [wLinkMenuSelectionSendBuffer]
+ and a
+ jr nz, asm_f547c
+ ld a, [wLinkMenuSelectionReceiveBuffer]
+ and a
+ jr nz, Func_f5476
+ xor a
+ ld [wUnknownSerialCounter], a
+ ld [wUnknownSerialCounter+1], a
+ and a
+ ret
+
+Func_f5476::
+ ld hl, ColosseumIneligibleText
+ call PrintText
+asm_f547c::
+ jp Func_f531b
+
+asm_f547f::
+ xor a
+ ld [wUnknownSerialCounter], a
+ ld [wUnknownSerialCounter+1], a
+ scf
+ ret
+
+PointerTable_f5488::
+ dw PokeCup
+ dw PikaCup
+ dw PetitCup
+
+PokeCup::
+ ld hl, wPartyCount
+ ld a, [hli]
+ cp $3
+ jp nz, NotThreeMonsInParty
+ ld b, $3
+.loop
+ ld a, [hli]
+ cp MEW
+ jp z, MewInParty
+ dec b
+ jr nz, .loop
+ dec hl
+ dec hl
+ cp [hl] ; is third mon second mon?
+ jp z, DuplicateSpecies
+ dec hl ; wPartySpecies
+ cp [hl] ; is third mon first mon?
+ jp z, DuplicateSpecies
+ ld a, [hli]
+ cp [hl] ; is first mon second mon?
+ jp z, DuplicateSpecies
+ ld a, [wPartyMon1Level]
+ cp 56
+ jp nc, LevelAbove55
+ cp 50
+ jp c, LevelUnder50
+ ld b, a
+ ld a, [wPartyMon2Level]
+ cp 56
+ jp nc, LevelAbove55
+ cp 50
+ jp c, LevelUnder50
+ ld c, a
+ ld a, [wPartyMon3Level]
+ cp 56
+ jp nc, LevelAbove55
+ cp 50
+ jp c, LevelUnder50
+ add b
+ add c
+ cp 156
+ jp nc, CombinedLevelsGreaterThan155
+ xor a
+ ret
+
+PikaCup::
+ ld hl, wPartyCount
+ ld a, [hli]
+ cp $3
+ jp nz, NotThreeMonsInParty
+ ld b, $3
+.loop
+ ld a, [hli] ; wPartySpecies
+ cp MEW
+ jp z, MewInParty
+ dec b
+ jr nz, .loop
+ dec hl
+ dec hl
+ cp [hl] ; is third mon second mon?
+ jp z, DuplicateSpecies
+ dec hl ; wPartySpecies
+ cp [hl] ; is third mon first mon?
+ jp z, DuplicateSpecies
+ ld a, [hli]
+ cp [hl] ; is first mon second mon?
+ jp z, DuplicateSpecies
+ ld a, [wPartyMon1Level]
+ cp 21
+ jp nc, LevelAbove20
+ cp 15
+ jp c, LevelUnder15
+ ld b, a
+ ld a, [wPartyMon2Level]
+ cp 21
+ jp nc, LevelAbove20
+ cp 15
+ jp c, LevelUnder15
+ ld c, a
+ ld a, [wPartyMon3Level]
+ cp 21
+ jp nc, LevelAbove20
+ cp 15
+ jp c, LevelUnder15
+ add b
+ add c
+ cp 51
+ jp nc, CombinedLevelsAbove50
+ xor a
+ ret
+
+PetitCup::
+ ld hl, wPartyCount
+ ld a, [hli]
+ cp $3
+ jp nz, NotThreeMonsInParty
+ ld b, $3
+.loop
+ ld a, [hli]
+ cp MEW
+ jp z, MewInParty
+ dec b
+ jr nz, .loop
+ dec hl
+ dec hl
+ cp [hl] ; is third mon second mon?
+ jp z, DuplicateSpecies
+ dec hl ; wPartySpecies
+ cp [hl] ; is third mon first mon?
+ jp z, DuplicateSpecies
+ ld a, [hli]
+ cp [hl] ; is first mon second mon?
+ jp z, DuplicateSpecies
+ dec hl
+ ld a, [hl]
+ ld [wcf91], a
+ push hl
+ callfar Func_3b10f
+ pop hl
+ jp c, asm_f56ad
+ inc hl
+ ld a, [hl]
+ ld [wcf91], a
+ push hl
+ callfar Func_3b10f
+ pop hl
+ jp c, asm_f56ad
+ inc hl
+ ld a, [hl]
+ ld [wcf91], a
+ push hl
+ callfar Func_3b10f
+ pop hl
+ jp c, asm_f56ad
+ dec hl
+ dec hl
+ ld b, $3
+.bigloop
+ ld a, [hli]
+ push hl
+ push bc
+ push af
+ dec a
+ ld c, a
+ ld b, $0
+ ld hl, PokedexEntryPointers
+ add hl, bc
+ add hl, bc
+ ld de, wcd6d
+ ld bc, $2
+ ld a, BANK(PokedexEntryPointers)
+ call FarCopyData
+ ld hl, wcd6d
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld de, wcd6d
+ ld bc, $14
+ ld a, BANK(PokedexEntryPointers)
+ call FarCopyData
+ ld hl, wcd6d
+.loop2
+ ld a, [hli]
+ cp "@"
+ jr nz, .loop2
+ ld a, [hli]
+ cp $7
+ jp nc, asm_f5689
+ add a
+ add a
+ ld b, a
+ add a
+ add b
+ ld b, a
+ ld a, [hli]
+ add b
+ cp $51
+ jp nc, asm_f5689
+ ld a, [hli]
+ sub $b9
+ ld a, [hl]
+ sbc $1
+ jp nc, asm_f569b
+ pop af
+ pop bc
+ pop hl
+ dec b
+ jr nz, .bigloop
+ ld a, [wPartyMon1Level]
+ cp 31
+ jp nc, LevelAbove30
+ cp 25
+ jp c, LevelUnder25
+ ld b, a
+ ld a, [wPartyMon2Level]
+ cp 31
+ jp nc, LevelAbove30
+ cp 25
+ jp c, LevelUnder25
+ ld c, a
+ ld a, [wPartyMon3Level]
+ cp 31
+ jp nc, LevelAbove30
+ cp 25
+ jp c, LevelUnder25
+ add b
+ add c
+ cp 81
+ jp nc, CombinedLevelsAbove80
+ xor a
+ ret
+
+NotThreeMonsInParty::
+ ld hl, Colosseum3MonsText
+ call PrintText
+ ld a, $1
+ ret
+
+MewInParty::
+ ld hl, ColosseumMewText
+ call PrintText
+ ld a, $2
+ ret
+
+DuplicateSpecies::
+ ld hl, ColosseumDifferentMonsText
+ call PrintText
+ ld a, $3
+ ret
+
+LevelAbove55::
+ ld hl, ColosseumMaxL55Text
+ call PrintText
+ ld a, $4
+ ret
+
+LevelUnder50::
+ ld hl, ColosseumMinL50Text
+ call PrintText
+ ld a, $5
+ ret
+
+CombinedLevelsGreaterThan155::
+ ld hl, ColosseumTotalL155Text
+ call PrintText
+ ld a, $6
+ ret
+
+LevelAbove30::
+ ld hl, ColosseumMaxL30Text
+ call PrintText
+ ld a, $7
+ ret
+
+LevelUnder25::
+ ld hl, ColosseumMinL25Text
+ call PrintText
+ ld a, $8
+ ret
+
+CombinedLevelsAbove80::
+ ld hl, ColosseumTotalL80Text
+ call PrintText
+ ld a, $9
+ ret
+
+LevelAbove20::
+ ld hl, ColosseumMaxL20Text
+ call PrintText
+ ld a, $a
+ ret
+
+LevelUnder15::
+ ld hl, ColosseumMinL15Text
+ call PrintText
+ ld a, $b
+ ret
+
+CombinedLevelsAbove50::
+ ld hl, ColosseumTotalL50Text
+ call PrintText
+ ld a, $c
+ ret
+
+asm_f5689::
+ pop af
+ pop bc
+ pop hl
+ ld [wd11e], a
+ call GetMonName
+ ld hl, ColosseumHeightText
+ call PrintText
+ ld a, $d
+ ret
+
+asm_f569b::
+ pop af
+ pop bc
+ pop hl
+ ld [wd11e], a
+ call GetMonName
+ ld hl, ColosseumWeightText
+ call PrintText
+ ld a, $e
+ ret
+
+asm_f56ad::
+ ld a, [hl]
+ ld [wd11e], a
+ call GetMonName
+ ld hl, ColosseumEvolvedText
+ call PrintText
+ ld a, $f
+ ret
+
+Func_f56bd::
+ xor a
+ ldh [hAutoBGTransferEnabled], a
+ coord hl, 1, 11
+ lb bc, 6, 18
+ call ClearScreenArea
+ ld a, [wCurrentMenuItem]
+ cp $3
+ jr nc, .asm_f56e6
+ ld hl, PointerTable_f56ee
+ ld a, [wCurrentMenuItem]
+ ld c, a
+ ld b, $0
+ add hl, bc
+ add hl, bc
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld d, h
+ ld e, l
+ coord hl, 1, 12
+ call PlaceString
+.asm_f56e6
+ call Delay3
+ ld a, $1
+ ldh [hAutoBGTransferEnabled], a
+ ret
+
+PointerTable_f56ee::
+ dw Text_f56f4
+ dw Text_f5728
+ dw Text_f575b
+
+Text_f56f4::
+ db "LVs of 3<PKMN>:50-55"
+ next "Sum of LVs:155 MAX"
+ next "MEW can't attend.@"
+
+Text_f5728::
+ db "LVs of 3<PKMN>:15-20"
+ next "Sum of LVs:50 MAX"
+ next "MEW can't attend.@"
+
+Text_f575b::
+ db "3 Basic <PKMN>.LV25-30"
+ next "Sum of LVs:80 MAX"
+ next "6’8” and 44lb MAX@"
+
+Text_f5791::
+ db "View"
+ next "Rules@"
+
+Text_f579c::
+ db "# Cup"
+ next "Pika Cup"
+ next "Petit Cup"
+ next "CANCEL@"
+
+Colosseum3MonsText::
+ text_far _Colosseum3MonsText
+ text_end
+
+ColosseumMewText::
+ text_far _ColosseumMewText
+ text_end
+
+ColosseumDifferentMonsText::
+ text_far _ColosseumDifferentMonsText
+ text_end
+
+ColosseumMaxL55Text::
+ text_far _ColosseumMaxL55Text
+ text_end
+
+ColosseumMinL50Text::
+ text_far _ColosseumMinL50Text
+ text_end
+
+ColosseumTotalL155Text::
+ text_far _ColosseumTotalL155Text
+ text_end
+
+ColosseumMaxL30Text::
+ text_far _ColosseumMaxL30Text
+ text_end
+
+ColosseumMinL25Text::
+ text_far _ColosseumMinL25Text
+ text_end
+
+ColosseumTotalL80Text::
+ text_far _ColosseumTotalL80Text
+ text_end
+
+ColosseumMaxL20Text::
+ text_far _ColosseumMaxL20Text
+ text_end
+
+ColosseumMinL15Text::
+ text_far _ColosseumMinL15Text
+ text_end
+
+ColosseumTotalL50Text::
+ text_far _ColosseumTotalL50Text
+ text_end
+
+ColosseumHeightText::
+ text_far _ColosseumHeightText
+ text_end
+
+ColosseumWeightText::
+ text_far _ColosseumWeightText
+ text_end
+
+ColosseumEvolvedText::
+ text_far _ColosseumEvolvedText
+ text_end
+
+ColosseumIneligibleText::
+ text_far _ColosseumIneligibleText
+ text_end
+
+LinkMenu:
+ xor a
+ ld [wLetterPrintingDelayFlags], a
+ ld hl, wd72e
+ set 6, [hl]
+ ld hl, TextTerminator_f5a16
+ call PrintText
+ call SaveScreenTilesToBuffer1
+ ld hl, ColosseumWhereToText
+ call PrintText
+ hlcoord 5, 3
+ lb bc, 8, 13
+ call TextBoxBorder
+ call UpdateSprites
+ hlcoord 7, 5
+ ld de, TradeCenterText
+ call PlaceString
+ xor a
+ ld [wUnusedCD37], a
+ ld [wd72d], a
+ ld [wd11e], a
+ ld hl, wTopMenuItemY
+ ld a, $5
+ ld [hli], a
+ ld a, $6
+ ld [hli], a
+ xor a
+ ld [hli], a
+ inc hl
+ ld a, $3
+ ld [hli], a
+ ld [hli], a
+ xor a
+ ld [hl], a
+.waitForInputLoop
+ call HandleMenuInput
+ and A_BUTTON | B_BUTTON
+ add a
+ add a
+ ld b, a
+ ld a, [wCurrentMenuItem]
+ cp $3
+ jr nz, .asm_f586b
+ bit 2, b
+ jr z, .asm_f586b
+ dec a
+ ld b, $8
+.asm_f586b
+ 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_f5c7d
+ ld a, [wLinkMenuSelectionReceiveBuffer + 1]
+ ld b, a
+ and $f0
+ cp $d0
+ jr nz, .exchangeMenuSelectionLoop
+.asm_f5c7d
+ 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.
+ ldh a, [hSerialConnectionStatus]
+ cp USING_INTERNAL_CLOCK
+ jr z, .doneChoosingMenuSelection
+.useEnemyMenuSelection
+ ld a, $1
+ ld [wd11e], a
+ ld a, b
+ ld [wLinkMenuSelectionSendBuffer], a
+ and $3
+ ld [wCurrentMenuItem], a ; wCurrentMenuItem
+.doneChoosingMenuSelection
+ ldh a, [hSerialConnectionStatus]
+ cp USING_INTERNAL_CLOCK
+ jr nz, .skipStartingTransfer
+ call DelayFrame
+ call DelayFrame
+ ld a, START_TRANSFER_INTERNAL_CLOCK
+ ldh [rSC], a
+.skipStartingTransfer
+ ld b, " "
+ ld c, " "
+ ld d, " "
+ ld e, "▷"
+ ld a, [wLinkMenuSelectionSendBuffer]
+ and (B_BUTTON << 2) ; was B button pressed?
+ jr nz, .updateCursorPosition
+; A button was pressed
+ ld a, [wCurrentMenuItem]
+ cp $2
+ jp z, .asm_f5963
+ ld b, e
+ ld e, c
+ ld a, [wCurrentMenuItem]
+ and a
+ jr z, .updateCursorPosition
+ ld c, b
+ ld b, d
+ dec a
+ jr z, .updateCursorPosition
+ ld d, c
+ ld c, b
+.updateCursorPosition
+ call Func_f59ec
+ 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, ColosseumPleaseWaitText
+ call PrintText
+ ld c, 50
+ call DelayFrames
+ ld hl, wd732
+ res 1, [hl]
+ ld a, [wDefaultMap]
+ ld [wDestinationMap], a
+ callfar 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
+ jpfar SpecialEnterMap
+.choseCancel
+ xor a
+ ld [wMenuJoypadPollCount], a
+ call Delay3
+ callfar CloseLinkConnection
+ ld hl, ColosseumCanceledText
+ call PrintText
+ ld hl, wd72e
+ res 6, [hl]
+ ret
+
+.asm_f5963
+ ld a, [wd11e]
+ and a
+ jr nz, .asm_f5974
+ ld b, " "
+ ld c, " "
+ ld d, "▷"
+ ld e, " "
+ call Func_f59ec
+.asm_f5974
+ xor a
+ ld [wBuffer], a
+ ld a, $ff
+ ld [wSerialExchangeNybbleReceiveData], a
+ ld a, $b
+ ld [wLinkMenuSelectionSendBuffer], a
+ ld b, $78
+.loop
+ ldh a, [hSerialConnectionStatus]
+ cp $2
+ call z, DelayFrame
+ dec b
+ jr z, .asm_f59b2
+ call Serial_ExchangeNybble
+ call DelayFrame
+ ld a, [wSerialExchangeNybbleReceiveData]
+ inc a
+ jr z, .loop
+ ld b, $f
+.loop2
+ call DelayFrame
+ call Serial_ExchangeNybble
+ dec b
+ jr nz, .loop2
+ ld b, $f
+.loop3
+ call DelayFrame
+ call Serial_SendZeroByte
+ dec b
+ jr nz, .loop3
+ jr .asm_f59d6
+
+.asm_f59b2
+ xor a
+ ld [wUnknownSerialCounter], a
+ ld [wUnknownSerialCounter+1], a
+ ld a, [wd11e]
+ and a
+ jr z, .asm_f59cd
+ ld b, " "
+ ld c, " "
+ ld d, " "
+ ld e, "▷"
+ call Func_f59ec
+ jp .choseCancel
+
+.asm_f59cd
+ ld hl, ColosseumVersionText
+ call PrintText
+ jp .choseCancel
+
+.asm_f59d6
+ ld b, " "
+ ld c, " "
+ ld d, "▷"
+ ld e, " "
+ call Func_f59ec
+ call Func_f531b
+ jp c, .choseCancel
+ ld a, $f0
+ jp .next
+
+Func_f59ec::
+ ld a, b
+ ldcoord_a 6, 5
+ ld a, c
+ ldcoord_a 6, 7
+ ld a, d
+ ldcoord_a 6, 9
+ ld a, e
+ ldcoord_a 6, 11
+ ld c, 40
+ call DelayFrames
+ ret
+
+ColosseumWhereToText:
+ text_far _ColosseumWhereToText
+ text_end
+
+ColosseumPleaseWaitText:
+ text_far _ColosseumPleaseWaitText
+ text_end
+
+ColosseumCanceledText:
+ text_far _ColosseumCanceledText
+ text_end
+
+ColosseumVersionText:
+ text_far _ColosseumVersionText
+ text_end
+
+TextTerminator_f5a16:
+ text_end
+
+TradeCenterText:
+ db "TRADE CENTER"
+ next "COLOSSEUM"
+ next "COLOSSEUM2"
+ next "CANCEL@"
diff --git a/engine/menus/main_menu.asm b/engine/menus/main_menu.asm
new file mode 100755
index 00000000..7d81d2f6
--- /dev/null
+++ b/engine/menus/main_menu.asm
@@ -0,0 +1,300 @@
+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 1
+ jr z, .noSaveFile
+; there's a save file
+ hlcoord 0, 0
+ lb bc, 6, 13
+ call TextBoxBorder
+ hlcoord 2, 2
+ ld de, ContinueText
+ call PlaceString
+ jr .next2
+.noSaveFile
+ hlcoord 0, 0
+ lb bc, 4, 13
+ call TextBoxBorder
+ hlcoord 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 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 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
+ ldh [hJoyPressed], a
+ ldh [hJoyReleased], a
+ ldh [hJoyHeld], a
+ call Joypad
+ ldh 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 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
+ ld a, 64 ; audio?
+ ld [wPrinterSettings], a
+ ret
+
+Func_5cc1:
+; unused?
+ ld a, $6d
+ cp $80
+ ret c ; will always be executed
+ ld hl, NotEnoughMemoryText
+ call PrintText
+ ret
+
+NotEnoughMemoryText:
+ text_far _NotEnoughMemoryText
+ text_end
+
+StartNewGame:
+ ld hl, wd732
+ res 1, [hl]
+StartNewGameDebug:
+ call OakSpeech
+ ld a, $8
+ ld [wPlayerMovingDirection], a
+ ld c, 20
+ call DelayFrames
+
+; enter map after using a special warp or loading the game from the main menu
+SpecialEnterMap::
+ xor a
+ ldh [hJoyPressed], a
+ ldh [hJoyHeld], a
+ ldh [hJoy5], a
+ ld [wd72d], a
+ ld hl, wd732
+ set 0, [hl] ; count play time
+ call ResetPlayerSpriteData
+ ld c, 20
+ call DelayFrames
+ call Func_5cc1
+ ld a, [wEnteringCableClub]
+ and a
+ ret nz
+ jp EnterMap
+
+ContinueText:
+ db "CONTINUE"
+ next ""
+ ; fallthrough
+
+NewGameText:
+ db "NEW GAME"
+ next "OPTION@"
+
+DisplayContinueGameInfo:
+ xor a
+ ldh [hAutoBGTransferEnabled], a
+ hlcoord 4, 7
+ lb bc, 8, 14
+ call TextBoxBorder
+ hlcoord 5, 9
+ ld de, SaveScreenInfoText
+ call PlaceString
+ hlcoord 12, 9
+ ld de, wPlayerName
+ call PlaceString
+ hlcoord 17, 11
+ call PrintNumBadges
+ hlcoord 16, 13
+ call PrintNumOwnedMons
+ hlcoord 13, 15
+ call PrintPlayTime
+ ld a, 1
+ ldh [hAutoBGTransferEnabled], a
+ ld c, 30
+ jp DelayFrames
+
+PrintSaveScreenText:
+ xor a
+ ldh [hAutoBGTransferEnabled], a
+ hlcoord 4, 0
+ lb bc, 8, 14
+ call TextBoxBorder
+ call LoadTextBoxTilePatterns
+ call UpdateSprites
+ hlcoord 5, 2
+ ld de, SaveScreenInfoText
+ call PlaceString
+ hlcoord 12, 2
+ ld de, wPlayerName
+ call PlaceString
+ hlcoord 17, 4
+ call PrintNumBadges
+ hlcoord 16, 6
+ call PrintNumOwnedMons
+ hlcoord 13, 8
+ call PrintPlayTime
+ ld a, $1
+ ldh [hAutoBGTransferEnabled], 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 "PLAYER"
+ next "BADGES "
+ next "#DEX "
+ next "TIME@"
+
+DisplayOptionMenu:
+ callfar DisplayOptionMenu_
+ ret
+
+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/engine/menus/naming_screen.asm b/engine/menus/naming_screen.asm
new file mode 100755
index 00000000..553c47d0
--- /dev/null
+++ b/engine/menus/naming_screen.asm
@@ -0,0 +1,508 @@
+AskName:
+ call SaveScreenTilesToBuffer1
+ call GetPredefRegisters
+ push hl
+ ld a, [wIsInBattle]
+ dec a
+ hlcoord 0, 0
+ lb bc, 4, 11
+ call z, ClearScreenArea ; only if in wild battle
+ ld a, [wcf91]
+ ld [wd11e], a
+ call GetMonName
+ ld hl, DoYouWantToNicknameText
+ call PrintText
+ hlcoord 14, 7
+ lb bc, 8, 15
+ 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, [wcf4b]
+ cp "@"
+ ret nz
+.declinedNickname
+ ld d, h
+ ld e, l
+ ld hl, wcd6d
+ ld bc, NAME_LENGTH
+ jp CopyData
+
+DoYouWantToNicknameText:
+ text_far _DoYouWantToNicknameText
+ text_end
+
+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, [wcf4b]
+ 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
+ farcall LoadMonPartySpriteGfx
+ hlcoord 0, 4
+ lb bc, 9, 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 [wcf4b], 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
+ farcall AnimatePartyMon_ForceSpeed1
+ pop af
+ ld [wCurrentMenuItem], a
+ call JoypadLowSensitivity
+ ldh 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, wcf4b
+ 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
+ jpfar 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 "゙"
+ ld de, Dakutens
+ jr z, .dakutensAndHandakutens
+ cp "゚"
+ 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:
+; In Red/Blue, the bank for the ED_tile was defined incorrectly as bank0
+; Luckily, the MBC3 treats loading $0 into $2000-$2fff range as loading bank1 into $4000-$7fff range
+; Because Yellow uses the MBC5, loading $0 into $2000 - $2fff range will load bank0 instead of bank1 and thus incorrectly load the tile
+; Instead of defining the correct bank, GameFreak decided to simply copy the ED_Tile in the function during HBlank
+ ld de, ED_Tile
+ ld hl, vFont tile $70
+ ld c, $4 ; number of copies needed
+.waitForHBlankLoop
+ ldh a, [rSTAT]
+ and %10 ; in HBlank?
+ jr nz, .waitForHBlankLoop
+ ld a, [de]
+ ld [hli], a
+ ld [hli], a
+ inc de
+ ld a, [de]
+ ld [hli], a
+ ld [hli], a
+ inc de
+ dec c
+ jr nz, .waitForHBlankLoop
+ ret
+
+ED_Tile:
+ INCBIN "gfx/font/ED.1bpp"
+ED_TileEnd:
+
+PrintAlphabet:
+ xor a
+ ldh [hAutoBGTransferEnabled], a
+ ld a, [wAlphabetCase]
+ and a
+ ld de, LowerCaseAlphabet
+ jr nz, .lowercase
+ ld de, UpperCaseAlphabet
+.lowercase
+ hlcoord 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
+ ldh [hAutoBGTransferEnabled], a
+ jp Delay3
+
+INCLUDE "data/text/alphabets.asm"
+
+PrintNicknameAndUnderscores:
+ call CalcStringLength
+ ld a, c
+ ld [wNamingScreenNameLength], a
+ hlcoord 10, 2
+ lb bc, 1, 10
+ call ClearScreenArea
+ hlcoord 10, 2
+ ld de, wcf4b
+ call PlaceString
+ hlcoord 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
+ hlcoord 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
+
+INCLUDE "data/text/dakutens.asm"
+
+; calculates the length of the string at wcf4b and stores it in c
+CalcStringLength:
+ ld hl, wcf4b
+ ld c, $0
+.loop
+ ld a, [hl]
+ cp "@"
+ ret z
+ inc hl
+ inc c
+ jr .loop
+
+PrintNamingText:
+ hlcoord 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
+ farcall WriteMonPartySpriteOAMBySpecies
+ pop af
+ ld [wd11e], a
+ call GetMonName
+ hlcoord 4, 1
+ call PlaceString
+ ld hl, $1
+ add hl, bc
+ ld [hl], "の" ; leftover from Japanese version; blank tile $c9 in English
+ hlcoord 1, 3
+ ld de, NicknameTextString
+ jr .placeString
+.notNickname
+ call PlaceString
+ ld l, c
+ ld h, b
+ ld de, NameTextString
+.placeString
+ jp PlaceString
+
+YourTextString:
+ db "YOUR @"
+
+RivalsTextString:
+ db "RIVAL's @"
+
+NameTextString:
+ db "NAME?@"
+
+NicknameTextString:
+ db "NICKNAME?@"
diff --git a/engine/menus/oaks_pc.asm b/engine/menus/oaks_pc.asm
new file mode 100755
index 00000000..7743e5df
--- /dev/null
+++ b/engine/menus/oaks_pc.asm
@@ -0,0 +1,28 @@
+OpenOaksPC:
+ call SaveScreenTilesToBuffer2
+ ld hl, AccessedOaksPCText
+ call PrintText
+ ld hl, GetDexRatedText
+ call PrintText
+ call YesNoChoice
+ ld a, [wCurrentMenuItem]
+ and a
+ jr nz, .closePC
+ predef DisplayDexRating
+.closePC
+ ld hl, ClosedOaksPCText
+ call PrintText
+ jp LoadScreenTilesFromBuffer2
+
+GetDexRatedText:
+ text_far _GetDexRatedText
+ text_end
+
+ClosedOaksPCText:
+ text_far _ClosedOaksPCText
+ text_waitbutton
+ text_end
+
+AccessedOaksPCText:
+ text_far _AccessedOaksPCText
+ text_end
diff --git a/engine/menus/options.asm b/engine/menus/options.asm
new file mode 100644
index 00000000..110bef2a
--- /dev/null
+++ b/engine/menus/options.asm
@@ -0,0 +1,443 @@
+DisplayOptionMenu_:
+ call InitOptionsMenu
+.optionMenuLoop
+ call JoypadLowSensitivity
+ ldh a, [hJoy5]
+ and START | B_BUTTON
+ jr nz, .exitOptionMenu
+ call OptionsControl
+ jr c, .dpadDelay
+ call GetOptionPointer
+ jr c, .exitOptionMenu
+.dpadDelay
+ call OptionsMenu_UpdateCursorPosition
+ call DelayFrame
+ call DelayFrame
+ call DelayFrame
+ jr .optionMenuLoop
+.exitOptionMenu
+ ret
+
+GetOptionPointer:
+ ld a, [wOptionsCursorLocation]
+ ld e, a
+ ld d, $0
+ ld hl, OptionMenuJumpTable
+ add hl, de
+ add hl, de
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ jp hl ; jump to the function for the current highlighted option
+
+OptionMenuJumpTable:
+ dw OptionsMenu_TextSpeed
+ dw OptionsMenu_BattleAnimations
+ dw OptionsMenu_BattleStyle
+ dw OptionsMenu_SpeakerSettings
+ dw OptionsMenu_GBPrinterBrightness
+ dw OptionsMenu_Dummy
+ dw OptionsMenu_Dummy
+ dw OptionsMenu_Cancel
+
+OptionsMenu_TextSpeed:
+ call GetTextSpeed
+ ldh a, [hJoy5]
+ bit 4, a ; right
+ jr nz, .pressedRight
+ bit 5, a
+ jr nz, .pressedLeft
+ jr .asm_41ce0
+.pressedRight
+ ld a, c
+ cp $2
+ jr c, .asm_41cca
+ ld c, $ff
+.asm_41cca
+ inc c
+ ld a, e
+ jr .asm_41cd6
+.pressedLeft
+ ld a, c
+ and a
+ jr nz, .asm_41cd4
+ ld c, $3
+.asm_41cd4
+ dec c
+ ld a, d
+.asm_41cd6
+ ld b, a
+ ld a, [wOptions]
+ and $f0
+ or b
+ ld [wOptions], a
+.asm_41ce0
+ ld b, $0
+ ld hl, TextSpeedStringsPointerTable
+ add hl, bc
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ hlcoord 14, 2
+ call PlaceString
+ and a
+ ret
+
+TextSpeedStringsPointerTable:
+ dw FastText
+ dw MidText
+ dw SlowText
+
+FastText:
+ db "FAST@"
+MidText:
+ db "MID @"
+SlowText:
+ db "SLOW@"
+
+GetTextSpeed:
+ ld a, [wOptions]
+ and $f
+ cp $5
+ jr z, .slowTextOption
+ cp $1
+ jr z, .fastTextOption
+; mid text option
+ ld c, $1
+ lb de, 1, 5
+ ret
+.slowTextOption
+ ld c, $2
+ lb de, 3, 1
+ ret
+.fastTextOption
+ ld c, $0
+ lb de, 5, 3
+ ret
+
+OptionsMenu_BattleAnimations:
+ ldh a, [hJoy5]
+ and D_RIGHT | D_LEFT
+ jr nz, .asm_41d33
+ ld a, [wOptions]
+ and $80 ; mask other bits
+ jr .asm_41d3b
+.asm_41d33
+ ld a, [wOptions]
+ xor $80
+ ld [wOptions], a
+.asm_41d3b
+ ld bc, $0
+ sla a
+ rl c
+ ld hl, AnimationOptionStringsPointerTable
+ add hl, bc
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ hlcoord 14, 4
+ call PlaceString
+ and a
+ ret
+
+AnimationOptionStringsPointerTable:
+ dw AnimationOnText
+ dw AnimationOffText
+
+AnimationOnText:
+ db "ON @"
+AnimationOffText:
+ db "OFF@"
+
+OptionsMenu_BattleStyle:
+ ldh a, [hJoy5]
+ and D_LEFT | D_RIGHT
+ jr nz, .asm_41d6b
+ ld a, [wOptions]
+ and $40 ; mask other bits
+ jr .asm_41d73
+.asm_41d6b
+ ld a, [wOptions]
+ xor $40
+ ld [wOptions], a
+.asm_41d73
+ ld bc, $0
+ sla a
+ sla a
+ rl c
+ ld hl, BattleStyleOptionStringsPointerTable
+ add hl, bc
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ hlcoord 14, 6
+ call PlaceString
+ and a
+ ret
+
+BattleStyleOptionStringsPointerTable:
+ dw BattleStyleShiftText
+ dw BattleStyleSetText
+
+BattleStyleShiftText:
+ db "SHIFT@"
+BattleStyleSetText:
+ db "SET @"
+
+OptionsMenu_SpeakerSettings:
+ ld a, [wOptions]
+ and $30
+ swap a
+ ld c, a
+ ldh a, [hJoy5]
+ bit 4, a
+ jr nz, .pressedRight
+ bit 5, a
+ jr nz, .pressedLeft
+ jr .asm_41dca
+.pressedRight
+ ld a, c
+ inc a
+ and $3
+ jr .asm_41dba
+.pressedLeft
+ ld a, c
+ dec a
+ and $3
+.asm_41dba
+ ld c, a
+ swap a
+ ld b, a
+ xor a
+ ldh [rNR51], a
+ ld a, [wOptions]
+ and $cf
+ or b
+ ld [wOptions], a
+.asm_41dca
+ ld b, $0
+ ld hl, SpeakerOptionStringsPointerTable
+ add hl, bc
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ hlcoord 8, 8
+ call PlaceString
+ and a
+ ret
+
+SpeakerOptionStringsPointerTable:
+ dw MonoSoundText
+ dw Earphone1SoundText
+ dw Earphone2SoundText
+ dw Earphone3SoundText
+
+MonoSoundText:
+ db "MONO @"
+Earphone1SoundText:
+ db "EARPHONE1@"
+Earphone2SoundText:
+ db "EARPHONE2@"
+Earphone3SoundText:
+ db "EARPHONE3@"
+
+OptionsMenu_GBPrinterBrightness:
+ call Func_41e7b
+ ldh a, [hJoy5]
+ bit 4, a
+ jr nz, .pressedRight
+ bit 5, a
+ jr nz, .pressedLeft
+ jr .asm_41e32
+.pressedRight
+ ld a, c
+ cp $4
+ jr c, .asm_41e22
+ ld c, $ff
+.asm_41e22
+ inc c
+ ld a, e
+ jr .asm_41e2e
+.pressedLeft
+ ld a, c
+ and a
+ jr nz, .asm_41e2c
+ ld c, $5
+.asm_41e2c
+ dec c
+ ld a, d
+.asm_41e2e
+ ld b, a
+ ld [wPrinterSettings], a
+.asm_41e32
+ ld b, $0
+ ld hl, GBPrinterOptionStringsPointerTable
+ add hl, bc
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ hlcoord 8, 10
+ call PlaceString
+ and a
+ ret
+
+GBPrinterOptionStringsPointerTable:
+ dw LightestPrintText
+ dw LighterPrintText
+ dw NormalPrintText
+ dw DarkerPrintText
+ dw DarkestPrintText
+
+LightestPrintText:
+ db "LIGHTEST@"
+LighterPrintText:
+ db "LIGHTER @"
+NormalPrintText:
+ db "NORMAL @"
+DarkerPrintText:
+ db "DARKER @"
+DarkestPrintText:
+ db "DARKEST @"
+
+Func_41e7b:
+ ld a, [wPrinterSettings]
+ and a
+ jr z, .asm_41e93
+ cp $20
+ jr z, .asm_41e99
+ cp $60
+ jr z, .asm_41e9f
+ cp $7f
+ jr z, .asm_41ea5
+ ld c, $2
+ lb de, $20, $60
+ ret
+.asm_41e93
+ ld c, $0
+ lb de, $7f, $20
+ ret
+.asm_41e99
+ ld c, $1
+ lb de, $0, $40
+ ret
+.asm_41e9f
+ ld c, $3
+ lb de, $40, $7f
+ ret
+.asm_41ea5
+ ld c, $4
+ lb de, $60, $0
+ ret
+
+OptionsMenu_Dummy:
+ and a
+ ret
+
+OptionsMenu_Cancel:
+ ldh a, [hJoy5]
+ and A_BUTTON
+ jr nz, .pressedCancel
+ and a
+ ret
+.pressedCancel
+ scf
+ ret
+
+OptionsControl:
+ ld hl, wOptionsCursorLocation
+ ldh a, [hJoy5]
+ cp D_DOWN
+ jr z, .pressedDown
+ cp D_UP
+ jr z, .pressedUp
+ and a
+ ret
+.pressedDown
+ ld a, [hl]
+ cp $7
+ jr nz, .doNotWrapAround
+ ld [hl], $0
+ scf
+ ret
+.doNotWrapAround
+ cp $4
+ jr c, .regularIncrement
+ ld [hl], $6
+.regularIncrement
+ inc [hl]
+ scf
+ ret
+.pressedUp
+ ld a, [hl]
+ cp $7
+ jr nz, .doNotMoveCursorToPrintOption
+ ld [hl], $4
+ scf
+ ret
+.doNotMoveCursorToPrintOption
+ and a
+ jr nz, .regularDecrement
+ ld [hl], $8
+.regularDecrement
+ dec [hl]
+ scf
+ ret
+
+OptionsMenu_UpdateCursorPosition:
+ hlcoord 1, 1
+ ld de, SCREEN_WIDTH
+ ld c, 16
+.loop
+ ld [hl], " "
+ add hl, de
+ dec c
+ jr nz, .loop
+ hlcoord 1, 2
+ ld bc, SCREEN_WIDTH * 2
+ ld a, [wOptionsCursorLocation]
+ call AddNTimes
+ ld [hl], "▶"
+ ret
+
+InitOptionsMenu:
+ hlcoord 0, 0
+ lb bc, SCREEN_HEIGHT - 2, SCREEN_WIDTH - 2
+ call TextBoxBorder
+ hlcoord 2, 2
+ ld de, AllOptionsText
+ call PlaceString
+ hlcoord 2, 16
+ ld de, OptionMenuCancelText
+ call PlaceString
+ xor a
+ ld [wOptionsCursorLocation], a
+ ld c, 5 ; the number of options to loop through
+.loop
+ push bc
+ call GetOptionPointer ; updates the next option
+ pop bc
+ ld hl, wOptionsCursorLocation
+ inc [hl] ; moves the cursor for the highlighted option
+ dec c
+ jr nz, .loop
+ xor a
+ ld [wOptionsCursorLocation], a
+ inc a
+ ldh [hAutoBGTransferEnabled], a
+ call Delay3
+ ret
+
+AllOptionsText:
+ db "TEXT SPEED :"
+ next "ANIMATION :"
+ next "BATTLESTYLE:"
+ next "SOUND:"
+ next "PRINT:@"
+
+OptionMenuCancelText:
+ db "CANCEL@"
diff --git a/engine/menus/party_menu.asm b/engine/menus/party_menu.asm
new file mode 100755
index 00000000..32f47f6a
--- /dev/null
+++ b/engine/menus/party_menu.asm
@@ -0,0 +1,314 @@
+DrawPartyMenu_::
+ xor a
+ ldh [hAutoBGTransferEnabled], a
+ call ClearScreen
+ call UpdateSprites
+ farcall LoadMonPartySpriteGfxWithLCDDisabled ; load pokemon icon graphics
+
+RedrawPartyMenu_::
+ ld a, [wPartyMenuTypeOrMessageID]
+ cp SWAP_MONS_PARTY_MENU
+ jp z, .printMessage
+ call ErasePartyMenuCursors
+ farcall InitPartyMenuBlkPacket
+ hlcoord 3, 0
+ ld de, wPartySpecies
+ xor a
+ ld c, a
+ ldh [hPartyMonIndex], a
+ ld [wWhichPartyMenuHPBar], a
+.loop
+ ld a, [de]
+ cp $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
+ ldh a, [hPartyMonIndex]
+ ld [wWhichPokemon], a
+ callfar IsThisPartymonStarterPikachu_Party
+ jr nc, .regularMon
+ call CheckPikachuFollowingPlayer
+ jr z, .regularMon
+ ld a, $ff
+ ldh [hPartyMonIndex], a
+.regularMon
+ farcall WriteMonPartySpriteOAMByPartyIndex ; place the appropriate pokemon icon
+ ld a, [wWhichPokemon]
+ inc a
+ ldh [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 TMHM_PARTY_MENU
+ jr z, .teachMoveMenu
+ cp 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
+ ldh a, [hFlagsFFFA]
+ set 0, a
+ ldh [hFlagsFFFA], a
+ add hl, bc
+ predef DrawHP2 ; draw HP bar and prints current / max HP
+ ldh a, [hFlagsFFFA]
+ res 0, a
+ ldh [hFlagsFFFA], 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
+ push hl
+ ld bc, 20 + 9 ; down 1 row and right 9 columns
+ 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 "ABLE@"
+.notAbleToLearnMoveText
+ db "NOT ABLE@"
+.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, wEvosMoves
+ ld a, BANK(EvosMovesPointerTable)
+ ld bc, 2
+ call FarCopyData
+ ld hl, wEvosMoves
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld de, wEvosMoves
+ ld a, BANK(EvosMovesPointerTable)
+ ld bc, wEvosMoves.end - wEvosMoves
+ call FarCopyData
+ ld hl, wEvosMoves
+ 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 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
+ pop hl
+ push hl
+ ld bc, 20 + 9 ; down 1 row and right 9 columns
+ add hl, bc
+ call PlaceString
+ pop hl
+ jr .printLevel
+.ableToEvolveText
+ db "ABLE@"
+.notAbleToEvolveText
+ db "NOT ABLE@"
+.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 FIRST_PARTY_MENU_TEXT_ID
+ 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
+ ldh [hAutoBGTransferEnabled], a
+ call Delay3
+ jp GBPalNormal
+.printItemUseMessage
+ and $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:
+ text_far _PartyMenuNormalText
+ text_end
+
+PartyMenuItemUseText:
+ text_far _PartyMenuItemUseText
+ text_end
+
+PartyMenuBattleText:
+ text_far _PartyMenuBattleText
+ text_end
+
+PartyMenuUseTMText:
+ text_far _PartyMenuUseTMText
+ text_end
+
+PartyMenuSwapMonText:
+ text_far _PartyMenuSwapMonText
+ text_end
+
+PotionText:
+ text_far _PotionText
+ text_end
+
+AntidoteText:
+ text_far _AntidoteText
+ text_end
+
+ParlyzHealText:
+ text_far _ParlyzHealText
+ text_end
+
+BurnHealText:
+ text_far _BurnHealText
+ text_end
+
+IceHealText:
+ text_far _IceHealText
+ text_end
+
+AwakeningText:
+ text_far _AwakeningText
+ text_end
+
+FullHealText:
+ text_far _FullHealText
+ text_end
+
+ReviveText:
+ text_far _ReviveText
+ text_end
+
+RareCandyText:
+ text_far _RareCandyText
+ sound_get_item_1 ; probably supposed to play SFX_LEVEL_UP but the wrong music bank is loaded
+ text_promptbutton
+ text_end
+
+SetPartyMenuHPBarColor:
+ ld hl, wPartyMenuHPBarColors
+ ld a, [wWhichPartyMenuHPBar]
+ ld c, a
+ ld b, 0
+ add hl, bc
+ call GetHealthBarColor
+ ld b, SET_PAL_PARTY_MENU_HP_BARS
+ call RunPaletteCommand
+ ld hl, wWhichPartyMenuHPBar
+ inc [hl]
+ ret
diff --git a/engine/menus/pc.asm b/engine/menus/pc.asm
new file mode 100755
index 00000000..4c340e7b
--- /dev/null
+++ b/engine/menus/pc.asm
@@ -0,0 +1,141 @@
+ActivatePC::
+ call SaveScreenTilesToBuffer2
+ ld a, SFX_TURN_ON_PC
+ call PlaySound
+ ld hl, TurnedOnPC1Text
+ call PrintText
+ call WaitForSoundToFinish
+ ld hl, wFlags_0xcd60
+ set 3, [hl]
+ call LoadScreenTilesFromBuffer2
+ call Delay3
+PCMainMenu:
+ farcall DisplayPCMainMenu
+ ld hl, wFlags_0xcd60
+ set 5, [hl]
+ call HandleMenuInput
+ bit 1, a ;if player pressed B
+ jp nz, LogOff
+ ld a, [wMaxMenuItem]
+ cp 2
+ jr nz, .next ;if not 2 menu items (not counting log off) (2 occurs before you get the pokedex)
+ ld a, [wCurrentMenuItem]
+ and a
+ jp z, BillsPC ;if current menu item id is 0, it's bills pc
+ cp 1
+ jr z, .playersPC ;if current menu item id is 1, it's players pc
+ jp LogOff ;otherwise, it's 2, and you're logging off
+.next
+ cp 3
+ jr nz, .next2 ;if not 3 menu items (not counting log off) (3 occurs after you get the pokedex, before you beat the pokemon league)
+ ld a, [wCurrentMenuItem]
+ and a
+ jp z, BillsPC ;if current menu item id is 0, it's bills pc
+ cp 1
+ jr z, .playersPC ;if current menu item id is 1, it's players pc
+ cp 2
+ jp z, OaksPC ;if current menu item id is 2, it's oaks pc
+ jp LogOff ;otherwise, it's 3, and you're logging off
+.next2
+ ld a, [wCurrentMenuItem]
+ and a
+ jp z, BillsPC ;if current menu item id is 0, it's bills pc
+ cp 1
+ jr z, .playersPC ;if current menu item id is 1, it's players pc
+ cp 2
+ jp z, OaksPC ;if current menu item id is 2, it's oaks pc
+ cp 3
+ jp z, PKMNLeague ;if current menu item id is 3, it's pkmnleague
+ jp LogOff ;otherwise, it's 4, and you're logging off
+.playersPC
+ ld hl, wFlags_0xcd60
+ res 5, [hl]
+ set 3, [hl]
+ ld a, SFX_ENTER_PC
+ call PlaySound
+ call WaitForSoundToFinish
+ ld hl, AccessedMyPCText
+ call PrintText
+ farcall PlayerPC
+ jr ReloadMainMenu
+OaksPC:
+ ld a, SFX_ENTER_PC
+ call PlaySound
+ call WaitForSoundToFinish
+ farcall OpenOaksPC
+ jr ReloadMainMenu
+PKMNLeague:
+ ld a, SFX_ENTER_PC
+ call PlaySound
+ call WaitForSoundToFinish
+ farcall PKMNLeaguePC
+ jr ReloadMainMenu
+BillsPC:
+ ld a, SFX_ENTER_PC
+ call PlaySound
+ call WaitForSoundToFinish
+ CheckEvent EVENT_MET_BILL
+ jr nz, .billsPC ;if you've met bill, use that bill's instead of someone's
+ ld hl, AccessedSomeonesPCText
+ jr .printText
+.billsPC
+ ld hl, AccessedBillsPCText
+.printText
+ call PrintText
+ farcall BillsPC_
+ReloadMainMenu:
+ xor a
+ ld [wDoNotWaitForButtonPressAfterDisplayingText], a
+ call ReloadMapData
+ call UpdateSprites
+ jp PCMainMenu
+LogOff:
+ ld a, SFX_TURN_OFF_PC
+ call PlaySound
+ call WaitForSoundToFinish
+ ld hl, wFlags_0xcd60
+ res 3, [hl]
+ res 5, [hl]
+ ret
+
+TurnedOnPC1Text:
+ text_far _TurnedOnPC1Text
+ text_end
+
+AccessedBillsPCText:
+ text_far _AccessedBillsPCText
+ text_end
+
+AccessedSomeonesPCText:
+ text_far _AccessedSomeonesPCText
+ text_end
+
+AccessedMyPCText:
+ text_far _AccessedMyPCText
+ text_end
+
+; removes one of the specified item ID [hItemToRemoveID] from bag (if existent)
+RemoveItemByID::
+ ld hl, wBagItems
+ ldh a, [hItemToRemoveID]
+ ld b, a
+ xor a
+ ldh [hItemToRemoveIndex], a
+.loop
+ ld a, [hli]
+ cp -1 ; reached terminator?
+ ret z
+ cp b
+ jr z, .foundItem
+ inc hl
+ ldh a, [hItemToRemoveIndex]
+ inc a
+ ldh [hItemToRemoveIndex], a
+ jr .loop
+.foundItem
+ ld a, $1
+ ld [wItemQuantity], a
+ ldh a, [hItemToRemoveIndex]
+ ld [wWhichPokemon], a
+ ld hl, wNumBagItems
+ jp RemoveItemFromInventory
diff --git a/engine/menus/players_pc.asm b/engine/menus/players_pc.asm
new file mode 100755
index 00000000..1fac030b
--- /dev/null
+++ b/engine/menus/players_pc.asm
@@ -0,0 +1,302 @@
+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
+ hlcoord 0, 0
+ lb bc, 8, 14
+ call TextBoxBorder
+ call UpdateSprites
+ hlcoord 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 "WITHDRAW ITEM"
+ next "DEPOSIT ITEM"
+ next "TOSS ITEM"
+ next "LOG OFF@"
+
+TurnedOnPC2Text:
+ text_far _TurnedOnPC2Text
+ text_end
+
+WhatDoYouWantText:
+ text_far _WhatDoYouWantText
+ text_end
+
+WhatToDepositText:
+ text_far _WhatToDepositText
+ text_end
+
+DepositHowManyText:
+ text_far _DepositHowManyText
+ text_end
+
+ItemWasStoredText:
+ text_far _ItemWasStoredText
+ text_end
+
+NothingToDepositText:
+ text_far _NothingToDepositText
+ text_end
+
+NoRoomToStoreText:
+ text_far _NoRoomToStoreText
+ text_end
+
+WhatToWithdrawText:
+ text_far _WhatToWithdrawText
+ text_end
+
+WithdrawHowManyText:
+ text_far _WithdrawHowManyText
+ text_end
+
+WithdrewItemText:
+ text_far _WithdrewItemText
+ text_end
+
+NothingStoredText:
+ text_far _NothingStoredText
+ text_end
+
+CantCarryMoreText:
+ text_far _CantCarryMoreText
+ text_end
+
+WhatToTossText:
+ text_far _WhatToTossText
+ text_end
+
+TossHowManyText:
+ text_far _TossHowManyText
+ text_end
diff --git a/engine/menus/pokedex.asm b/engine/menus/pokedex.asm
new file mode 100755
index 00000000..874fe50f
--- /dev/null
+++ b/engine/menus/pokedex.asm
@@ -0,0 +1,745 @@
+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
+ ldh [hJoy7], a
+.setUpGraphics
+ callfar LoadPokedexTilePatterns
+.loop
+ ld b, SET_PAL_GENERIC
+ call RunPaletteCommand
+.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
+ ldh [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
+ dec b
+ jr z, .loop
+ 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, 8
+ 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, 4
+ 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
+ ldh [hJoy7], 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
+ dec a
+ jr z, .chosePrint
+.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
+ ld a, $1
+ ldh [hJoy7], a
+ push bc
+ hlcoord 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
+ hlcoord 15, 8
+ ld de, 20
+ lb bc, " ", 9
+ 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
+
+.chosePrint
+ ldh a, [hTilesetType]
+ push af
+ xor a
+ ldh [hTilesetType], a
+ ld a, [wd11e]
+ ld [wcf91], a
+ callfar PrintPokedexEntry
+ xor a
+ ldh [hAutoBGTransferEnabled], a
+ call ClearScreen
+ pop af
+ ldh [hTilesetType], a
+ ld b, $3
+ 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:
+ call Pokedex_DrawInterface
+.loop
+ call Pokedex_PlacePokemonList
+ call GBPalNormal
+ call HandleMenuInput
+ bit BIT_B_BUTTON, a ; was the B button pressed?
+ jp nz, .buttonBPressed
+ bit BIT_A_BUTTON, a ; was the A button pressed?
+ jp nz, .buttonAPressed
+.checkIfUpPressed
+ bit BIT_D_UP, 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 BIT_D_DOWN, 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 BIT_D_RIGHT, 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 BIT_D_LEFT, 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
+
+Pokedex_DrawInterface:
+ xor a
+ ldh [hAutoBGTransferEnabled], a
+; draw the horizontal line separating the seen and owned amounts from the menu
+ hlcoord 15, 6
+ ld a, "─"
+ ld [hli], a
+ ld [hli], a
+ ld [hli], a
+ ld [hli], a
+ ld [hli], a
+ hlcoord 14, 0
+ ld [hl], $71 ; vertical line tile
+ hlcoord 14, 1
+ call DrawPokedexVerticalLine
+ hlcoord 14, 9
+ call DrawPokedexVerticalLine
+ ld hl, wPokedexSeen
+ ld b, wPokedexSeenEnd - wPokedexSeen
+ call CountSetBits
+ ld de, wNumSetBits
+ hlcoord 16, 2
+ lb bc, 1, 3
+ call PrintNumber ; print number of seen pokemon
+ ld hl, wPokedexOwned
+ ld b, wPokedexOwnedEnd - wPokedexOwned
+ call CountSetBits
+ ld de, wNumSetBits
+ hlcoord 16, 5
+ lb bc, 1, 3
+ call PrintNumber ; print number of owned pokemon
+ hlcoord 16, 1
+ ld de, PokedexSeenText
+ call PlaceString
+ hlcoord 16, 4
+ ld de, PokedexOwnText
+ call PlaceString
+ hlcoord 1, 1
+ ld de, PokedexContentsText
+ call PlaceString
+ hlcoord 16, 8
+ 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
+ ret
+
+DrawPokedexVerticalLine:
+ ld c, 9 ; height of line
+ ld de, SCREEN_WIDTH ; width of screen
+ 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 "SEEN@"
+
+PokedexOwnText:
+ db "OWN@"
+
+PokedexContentsText:
+ db "CONTENTS@"
+
+PokedexMenuItemsText:
+ db "DATA"
+ next "CRY"
+ next "AREA"
+ next "PRNT"
+ next "QUIT@"
+
+Pokedex_PlacePokemonList:
+ xor a
+ ldh [hAutoBGTransferEnabled], a
+ hlcoord 4, 2
+ lb bc, 14, 10
+ call ClearScreenArea
+ hlcoord 1, 3
+ ld a, [wListScrollOffset]
+ ld [wd11e], a
+ ld d, 7
+ ld a, [wDexMaxSeenMon]
+ cp 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
+ ldh [hAutoBGTransferEnabled], a
+ call Delay3
+ ret
+
+; 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
+ callfar 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
+ ldh [rNR50], a
+ ldh a, [hTilesetType]
+ push af
+ xor a
+ ldh [hTilesetType], a
+ call GBPalWhiteOut ; zero all palettes
+ ld a, [wd11e] ; pokemon ID
+ ld [wcf91], a
+ push af
+ ld b, SET_PAL_POKEDEX
+ call RunPaletteCommand
+ pop af
+ ld [wd11e], a
+ call DrawDexEntryOnScreen
+ call c, Pokedex_PrintFlavorTextAtRow11
+.waitForButtonPress
+ call JoypadLowSensitivity
+ ldh a, [hJoy5]
+ and a, A_BUTTON | B_BUTTON
+ jr z, .waitForButtonPress
+ pop af
+ ldh [hTilesetType], a
+ call GBPalWhiteOut
+ call ClearScreen
+ call RunDefaultPaletteCommand
+ call LoadTextBoxTilePatterns
+ call GBPalNormal
+ ld hl, wd72c
+ res 1, [hl]
+ ld a, $77 ; max volume
+ ldh [rNR50], a
+ ret
+
+HeightWeightText:
+ db "HT ?′??″"
+ next "WT ???lb@"
+
+; 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, $69, $6B, $69, $6B, $6B
+ db $6B, $6B, $69, $6B, $69, $6B, $69, $6B, $69, $6A
+ db "@"
+
+DrawDexEntryOnScreen:
+ call ClearScreen
+
+ hlcoord 0, 0
+ ld de, 1
+ lb bc, $64, SCREEN_WIDTH
+ call DrawTileLine ; draw top border
+
+ hlcoord 0, 17
+ ld b, $6f
+ call DrawTileLine ; draw bottom border
+
+ hlcoord 0, 1
+ ld de, 20
+ lb bc, $66, $10
+ call DrawTileLine ; draw left border
+
+ hlcoord 19, 1
+ ld b, $67
+ call DrawTileLine ; draw right border
+
+ ld a, $63 ; upper left corner tile
+ ldcoord_a 0, 0
+ ld a, $65 ; upper right corner tile
+ ldcoord_a 19, 0
+ ld a, $6c ; lower left corner tile
+ ldcoord_a 0, 17
+ ld a, $6e ; lower right corner tile
+ ldcoord_a 19, 17
+
+ hlcoord 0, 9
+ ld de, PokedexDataDividerLine
+ call PlaceString ; draw horizontal divider line
+
+ hlcoord 9, 6
+ ld de, HeightWeightText
+ call PlaceString
+
+ call GetMonName
+ hlcoord 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
+
+ hlcoord 9, 4
+ call PlaceString ; print species name
+
+ ld h, b
+ ld l, c
+ push de
+ ld a, [wd11e]
+ push af
+ call IndexToPokedex
+
+ hlcoord 2, 8
+ ld a, "№"
+ ld [hli], a
+ ld a, "<DOT>"
+ 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
+ hlcoord 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
+ ret z ; 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
+ hlcoord 12, 6
+ lb bc, 1, 2
+ call PrintNumber ; print feet (height)
+ ld a, "′"
+ ld [hl], a
+ inc de
+ inc de ; de = address of inches (height)
+ hlcoord 15, 6
+ lb bc, LEADING_ZEROES | 1, 2
+ call PrintNumber ; print inches (height)
+ ld a, "″"
+ ld [hl], a
+; now print the weight (note that weight is stored in tenths of pounds internally)
+ inc de
+ inc de
+ inc de ; de = address of upper byte of weight
+ 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
+ hlcoord 11, 8
+ lb bc, 2, 5 ; 2 bytes, 5 digits
+ call PrintNumber ; print weight
+ hlcoord 14, 8
+ ldh a, [hDexWeight + 1]
+ sub 10
+ ldh a, [hDexWeight]
+ sbc 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], "<DOT>" ; decimal point tile
+ pop af
+ ldh [hDexWeight + 1], a ; restore original value of [hDexWeight + 1]
+ pop af
+ ldh [hDexWeight], a ; restore original value of [hDexWeight]
+ pop hl
+ inc hl ; hl = address of pokedex description text
+ scf
+ ret
+
+Pokedex_PrintFlavorTextAtRow11:
+ bccoord 1, 11
+Pokedex_PrintFlavorTextAtBC:
+ ld a, %10
+ ldh [hClearLetterPrintingDelayFlags], a
+ call TextCommandProcessor ; print pokedex description text
+ xor a
+ ldh [hClearLetterPrintingDelayFlags], a
+ ret
+
+Pokedex_PrepareDexEntryForPrinting:
+ hlcoord 0, 0
+ ld de, SCREEN_WIDTH
+ lb bc, $66, $d
+ call DrawTileLine
+ hlcoord 19, 0
+ ld b, $67
+ call DrawTileLine
+ hlcoord 0, 13
+ ld de, $1
+ lb bc, $6f, SCREEN_WIDTH
+ call DrawTileLine
+ ld a, $6c
+ ldcoord_a 0, 13
+ ld a, $6e
+ ldcoord_a 19, 13
+ ld a, [wPrinterPokedexEntryTextPointer]
+ ld l, a
+ ld a, [wPrinterPokedexEntryTextPointer + 1]
+ ld h, a
+ bccoord 1, 1
+ ldh a, [hFlagsFFFA]
+ set 3, a
+ ldh [hFlagsFFFA], a
+ call Pokedex_PrintFlavorTextAtBC
+ ldh a, [hFlagsFFFA]
+ res 3, a
+ ldh [hFlagsFFFA], a
+ ret
+
+; 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/pokemon/dex_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/pokemon/dex_order.asm"
diff --git a/engine/menus/save.asm b/engine/menus/save.asm
new file mode 100755
index 00000000..bcb27ddf
--- /dev/null
+++ b/engine/menus/save.asm
@@ -0,0 +1,682 @@
+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:
+ text_far _FileDataDestroyedText
+ text_end
+
+LoadSAV0:
+ call EnableSRAMAndLatchClockData
+ ld a, $1
+ 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]
+ ldh [hTilesetType], a
+ ld hl, sCurBoxData
+ ld de, wBoxDataStart
+ ld bc, wBoxDataEnd - wBoxDataStart
+ call CopyData
+ and a
+ jp SAVGoodChecksum
+
+LoadSAV1:
+ call EnableSRAMAndLatchClockData
+ ld a, $1
+ 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:
+ call EnableSRAMAndLatchClockData
+ ld a, $1
+ 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:
+ call DisableSRAMAndPrepareClockData
+ ret
+
+LoadSAVIgnoreBadCheckSum:
+; unused function that loads save data and ignores bad checksums
+ call LoadSAV0
+ call LoadSAV1
+ jp LoadSAV2
+
+SaveSAV:
+ farcall PrintSaveScreenText
+ ld c, 10
+ call DelayFrames
+ ld hl, WouldYouLikeToSaveText
+ call SaveSAVConfirm
+ and a ;|0 = Yes|1 = No|
+ ret nz
+ ld c, 10
+ call DelayFrames
+ ld a, [wSaveFileStatus]
+ cp $1
+ jr z, .save
+ call SAVCheckRandomID
+ jr z, .save
+ ld hl, OlderFileWillBeErasedText
+ call SaveSAVConfirm
+ and a
+ ret nz
+.save
+ call SaveSAVtoSRAM
+ ld hl, SavingText
+ call PrintText
+ ld c, 128
+ call DelayFrames
+ ld hl, GameSavedText
+ call PrintText
+ ld c, 10
+ call DelayFrames
+ ld a, SFX_SAVE
+ call PlaySoundWaitForCurrent
+ call WaitForSoundToFinish
+ ld c, 30
+ call DelayFrames
+ ret
+
+SaveSAVConfirm:
+ call PrintText
+ hlcoord 0, 7
+ lb bc, 8, 1
+ ld a, TWO_OPTION_MENU
+ ld [wTextBoxID], a
+ call DisplayTextBoxID ; yes/no menu
+ ld a, [wCurrentMenuItem]
+ ret
+
+WouldYouLikeToSaveText:
+ text_far _WouldYouLikeToSaveText
+ text_end
+
+SavingText:
+ text_far _SavingText
+ text_end
+
+GameSavedText:
+ text_far _GameSavedText
+ text_end
+
+OlderFileWillBeErasedText:
+ text_far _OlderFileWillBeErasedText
+ text_end
+
+SaveSAVtoSRAM0:
+ call EnableSRAMAndLatchClockData
+ ld a, $1
+ 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
+ ldh a, [hTilesetType]
+ ld [sTilesetType], a
+ ld hl, sPlayerName
+ ld bc, sMainDataCheckSum - sPlayerName
+ call SAVCheckSum
+ ld [sMainDataCheckSum], a
+ call DisableSRAMAndPrepareClockData
+ ret
+
+SaveSAVtoSRAM1:
+; stored pokémon
+ call EnableSRAMAndLatchClockData
+ ld a, $1
+ 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
+ call DisableSRAMAndPrepareClockData
+ ret
+
+SaveSAVtoSRAM2:
+ call EnableSRAMAndLatchClockData
+ ld a, $1
+ 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, wPikachuHappiness
+ ld de, sMainData + $179
+ ld a, [hli]
+ ld [de], a
+ inc de
+ ld a, [hl]
+ ld [de], a
+ ld hl, sPlayerName
+ ld bc, sMainDataCheckSum - sPlayerName
+ call SAVCheckSum
+ ld [sMainDataCheckSum], a
+ call DisableSRAMAndPrepareClockData
+ 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, hFlagsFFFA
+ set 1, [hl]
+ call HandleMenuInput
+ ld hl, hFlagsFFFA
+ res 1, [hl]
+ bit 1, a ; pressed b
+ ret nz
+ ld a, $b6
+ call PlaySoundWaitForCurrent
+ call WaitForSoundToFinish
+ 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
+ ret
+
+WhenYouChangeBoxText:
+ text_far _WhenYouChangeBoxText
+ text_end
+
+CopyBoxToOrFromSRAM:
+; copy an entire box from hl to de with b as the SRAM bank
+ push hl
+ call EnableSRAMAndLatchClockData
+ 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
+ call DisableSRAMAndPrepareClockData
+ ret
+
+DisplayChangeBoxMenu:
+ xor a
+ ldh [hAutoBGTransferEnabled], 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
+ hlcoord 0, 0
+ lb bc, 2, 9
+ call TextBoxBorder
+ ld hl, ChooseABoxText
+ call PrintText
+ hlcoord 11, 0
+ lb bc, 12, 7
+ call TextBoxBorder
+ ld hl, hFlagsFFFA
+ set 2, [hl]
+ ld de, BoxNames
+ hlcoord 13, 1
+ call PlaceString
+ ld hl, hFlagsFFFA
+ res 2, [hl]
+ ld a, [wCurrentBoxNum]
+ and $7f
+ cp 9
+ jr c, .singleDigitBoxNum
+ sub 9
+ hlcoord 8, 2
+ ld [hl], "1"
+ add "0"
+ jr .next
+.singleDigitBoxNum
+ add "1"
+.next
+ ldcoord_a 9, 2
+ hlcoord 1, 2
+ ld de, BoxNoText
+ call PlaceString
+ call GetMonCountsForAllBoxes
+ hlcoord 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
+ ldh [hAutoBGTransferEnabled], a
+ ret
+
+ChooseABoxText:
+ text_far _ChooseABoxText
+ text_end
+
+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 No.@"
+
+EmptyAllSRAMBoxes:
+; marks all boxes in SRAM as empty (initialisation for the first time the
+; player changes the box)
+ call EnableSRAMAndLatchClockData
+ ld a, 2
+ ld [MBC1SRamBank], a
+ call EmptySRAMBoxesInBank
+ ld a, 3
+ ld [MBC1SRamBank], a
+ call EmptySRAMBoxesInBank
+ call DisableSRAMAndPrepareClockData
+ 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
+ call EnableSRAMAndLatchClockData
+ ld a, $2
+ ld [MBC1SRamBank], a
+ call GetMonCountsForBoxesInBank
+ ld a, $3
+ ld [MBC1SRamBank], a
+ call GetMonCountsForBoxesInBank
+ call DisableSRAMAndPrepareClockData
+ 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
+; and the two random numbers generated at game beginning
+; (which are stored at wPlayerID)s
+ call EnableSRAMAndLatchClockData
+ ld a, $01
+ 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 + (wPlayerID - wMainDataStart) ; 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:
+ call EnableSRAMAndLatchClockData
+ xor a
+ ld [MBC1SRamBank], a
+ call CopyData
+ call DisableSRAMAndPrepareClockData
+ ret
+
+ClearSAV:
+ call EnableSRAMAndLatchClockData
+ ld a, $4
+.loop
+ dec a
+ push af
+ call PadSRAM_FF
+ pop af
+ jr nz, .loop
+ call DisableSRAMAndPrepareClockData
+ ret
+
+PadSRAM_FF:
+ ld [MBC1SRamBank], a
+ ld hl, SRAM_Begin
+ ld bc, SRAM_End - SRAM_Begin
+ ld a, $ff
+ jp FillMemory
+
+EnableSRAMAndLatchClockData:
+ ld a, $1
+ ld [MBC1SRamBankingMode], a
+ ld a, SRAM_ENABLE
+ ld [MBC1SRamEnable], a
+ ret
+
+DisableSRAMAndPrepareClockData:
+ ld a, SRAM_DISABLE
+ ld [MBC1SRamBankingMode], a
+ ld [MBC1SRamEnable], a
+ ret
diff --git a/engine/menus/start_sub_menus.asm b/engine/menus/start_sub_menus.asm
new file mode 100755
index 00000000..84583a69
--- /dev/null
+++ b/engine/menus/start_sub_menus.asm
@@ -0,0 +1,825 @@
+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
+ ldh 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 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 BIT_THUNDERBADGE, a
+ 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?
+ jr nz, .asm_5d4c
+ call LoadFontTilePatterns
+ ld hl, wd72e
+ set 1, [hl]
+ jp StartMenu_Pokemon
+.asm_5d4c
+ call Func_1510
+ jp .goBackToMap
+.cut
+ bit BIT_CASCADEBADGE, a
+ jp z, .newBadgeRequired
+ predef UsedCut
+ ld a, [wActionResultOrTookBattleTurn]
+ and a
+ jp z, .loop
+ jp CloseTextDisplay
+.surf
+ bit BIT_SOULBADGE, a
+ jp z, .newBadgeRequired
+ farcall IsSurfingAllowed
+ ld hl, wd728
+ bit 1, [hl]
+ res 1, [hl]
+ jp z, .loop
+ ld a, [wcf91]
+ cp PIKACHU ; is this surfing pikachu?
+ jr z, .surfingPikachu
+ ld a, $1
+ jr .continue
+.surfingPikachu
+ ld a, $2
+.continue
+ ld [wd473], a
+ ld a, SURFBOARD
+ ld [wcf91], a
+ ld [wPseudoItemID], a
+ call UseItem
+ ld a, [wActionResultOrTookBattleTurn]
+ and a
+ jr z, .reloadNormalSprite
+ call GBPalWhiteOutWithDelay3
+ jp .goBackToMap
+.reloadNormalSprite
+ xor a
+ ld [wd473], a
+ jp .loop
+.strength
+ bit BIT_RAINBOWBADGE, a
+ jp z, .newBadgeRequired
+ predef PrintStrengthTxt
+ call GBPalWhiteOutWithDelay3
+ jp .goBackToMap
+.flash
+ bit BIT_BOULDERBADGE, a
+ jp z, .newBadgeRequired
+ xor a
+ ld [wMapPalOffset], a
+ ld hl, .flashLightsAreaText
+ call PrintText
+ call GBPalWhiteOutWithDelay3
+ jp .goBackToMap
+.flashLightsAreaText
+ text_far _FlashLightsAreaText
+ text_end
+.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]
+ call Func_1510
+ ld hl, wd72e
+ set 1, [hl]
+ res 4, [hl]
+ ld c, 60
+ call DelayFrames
+ call GBPalWhiteOutWithDelay3
+ jp .goBackToMap
+.warpToLastPokemonCenterText
+ text_far _WarpToLastPokemonCenterText
+ text_end
+.cannotUseTeleportNowText
+ text_far _CannotUseTeleportNowText
+ text_end
+.cannotFlyHereText
+ text_far _CannotFlyHereText
+ text_end
+.softboiled
+ ld hl, wPartyMon1MaxHP
+ ld a, [wWhichPokemon]
+ ld bc, wPartyMon2 - wPartyMon1
+ call AddNTimes
+ ld a, [hli]
+ ldh [hDividend], a
+ ld a, [hl]
+ ldh [hDividend + 1], a
+ ld a, 5
+ ldh [hDivisor], a
+ ld b, 2 ; number of bytes
+ call Divide
+ ld bc, wPartyMon1HP - wPartyMon1MaxHP
+ add hl, bc
+ ld a, [hld]
+ ld b, a
+ ldh a, [hQuotient + 3]
+ sub b
+ ld b, [hl]
+ ldh a, [hQuotient + 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
+ text_far _NotHealthyEnoughText
+ text_end
+.goBackToMap
+ call RestoreScreenTilesAndReloadTilePatterns
+ jp CloseTextDisplay
+.newBadgeRequired
+ ld hl, .newBadgeRequiredText
+ call PrintText
+ jp .loop
+.newBadgeRequiredText
+ text_far _NewBadgeRequiredText
+ text_end
+
+; writes a blank tile to all possible menu cursor positions on the party menu
+ErasePartyMenuCursors::
+ hlcoord 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 hl, wListPointer
+ ld [hl], wNumBagItems & $ff
+ inc hl
+ ld [hl], wNumBagItems / $100 ; 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, " "
+ ldcoord_a 5, 4
+ ldcoord_a 5, 6
+ ldcoord_a 5, 8
+ ldcoord_a 5, 10
+ call PlaceUnfilledArrowMenuCursor
+ xor a
+ ld [wMenuItemToSwap], a
+ ld a, [wcf91]
+ cp 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 CopyStringToCF4B ; copy name to wcf4b
+ ld a, [wcf91]
+ cp 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 HM01
+ 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 $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:
+ text_far _CannotUseItemsHereText
+ text_end
+
+CannotGetOffHereText:
+ text_far _CannotGetOffHereText
+ text_end
+
+INCLUDE "data/items/use_party.asm"
+
+INCLUDE "data/items/use_overworld.asm"
+
+StartMenu_TrainerInfo::
+ call GBPalWhiteOut
+ call ClearScreen
+ call UpdateSprites
+ ldh a, [hTilesetType]
+ push af
+ xor a
+ ldh [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
+ farcall DrawStartMenu ; XXX what difference does this make?
+ call LoadGBPal
+ pop af
+ ldh [hTilesetType], a
+ jp RedisplayStartMenu_DoNotDrawStartMenu
+
+; 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
+ hlcoord 0, 2
+ ld a, " "
+ call TrainerInfo_DrawVerticalLine
+ hlcoord 1, 2
+ call TrainerInfo_DrawVerticalLine
+ ld hl, vChars2 tile $07
+ ld de, vChars2 tile $00
+ ld bc, $1c tiles
+ call CopyData
+ ld hl, TrainerInfoTextBoxTileGraphics ; trainer info text box tile patterns
+ ld de, vChars2 tile $77
+ ld bc, 8 tiles
+ push bc
+ call TrainerInfo_FarCopyData
+ ld hl, BlankLeaderNames
+ ld de, vChars2 tile $60
+ ld bc, $17 tiles
+ call TrainerInfo_FarCopyData
+ pop bc
+ ld hl, BadgeNumbersTileGraphics ; badge number tile patterns
+ ld de, vChars1 tile $58
+ call TrainerInfo_FarCopyData
+ ld hl, GymLeaderFaceAndBadgeTileGraphics ; gym leader face and badge tile patterns
+ ld de, vChars2 tile $20
+ ld bc, 8 * 8 tiles
+ ld a, BANK(GymLeaderFaceAndBadgeTileGraphics)
+ call FarCopyData
+ ld hl, TextBoxGraphics
+ ld de, 13 tiles
+ add hl, de ; hl = colon tile pattern
+ ld de, vChars1 tile $56
+ ld bc, 1 tiles
+ ld a, BANK(TextBoxGraphics)
+ push bc
+ call FarCopyData
+ pop bc
+ ld hl, TrainerInfoTextBoxTileGraphics tile 8 ; background tile pattern
+ ld de, vChars1 tile $57
+ call TrainerInfo_FarCopyData
+ call EnableLCD
+ ld hl, wTrainerInfoTextBoxWidthPlus1
+ ld a, 18 + 1
+ ld [hli], a
+ dec a
+ ld [hli], a
+ ld [hl], 1
+ hlcoord 0, 0
+ call TrainerInfo_DrawTextBox
+ ld hl, wTrainerInfoTextBoxWidthPlus1
+ ld a, 16 + 1
+ ld [hli], a
+ dec a
+ ld [hli], a
+ ld [hl], 3
+ hlcoord 1, 10
+ call TrainerInfo_DrawTextBox
+ hlcoord 0, 10
+ ld a, $d7
+ call TrainerInfo_DrawVerticalLine
+ hlcoord 19, 10
+ call TrainerInfo_DrawVerticalLine
+ hlcoord 6, 9
+ ld de, TrainerInfo_BadgesText
+ call PlaceString
+ hlcoord 2, 2
+ ld de, TrainerInfo_NameMoneyTimeText
+ call PlaceString
+ hlcoord 7, 2
+ ld de, wPlayerName
+ call PlaceString
+ hlcoord 8, 4
+ ld de, wPlayerMoney
+ ld c, $e3
+ call PrintBCDNumber
+ hlcoord 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 FarCopyData
+
+TrainerInfo_NameMoneyTimeText:
+ db "NAME/"
+ next "MONEY/"
+ next "TIME/@"
+
+; $76 is a circle tile
+TrainerInfo_BadgesText:
+ db $76,"BADGES",$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
+ ldh [hAutoBGTransferEnabled], a
+ call ClearScreen
+ call UpdateSprites
+ callfar 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
+ hlcoord 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]
+ ldh [hSwapTemp], a
+ ld a, [de]
+ ld [hl], a
+ ldh 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/engine/menus/swap_items.asm b/engine/menus/swap_items.asm
new file mode 100644
index 00000000..2d506ce2
--- /dev/null
+++ b/engine/menus/swap_items.asm
@@ -0,0 +1,149 @@
+HandleItemListSwapping::
+ ld a, [wListMenuID]
+ cp ITEMLISTMENU
+ jp nz, DisplayListMenuIDLoop ; only rearrange item list menus
+ push hl
+ ld hl, wListPointer
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ inc hl ; hl = beginning of list entries
+ ld a, [wCurrentMenuItem]
+ ld b, a
+ ld a, [wListScrollOffset]
+ add b
+ add a
+ ld c, a
+ ld b, 0
+ add hl, bc ; hl = address of currently selected item entry
+ ld a, [hl]
+ pop hl
+ inc a
+ jp z, DisplayListMenuIDLoop ; ignore attempts to swap the Cancel menu item
+ ld a, [wMenuItemToSwap] ; ID of item chosen for swapping (counts from 1)
+ and a ; has the first item to swap already been chosen?
+ jr nz, .swapItems
+; if not, set the currently selected item as the first item
+ ld a, [wCurrentMenuItem]
+ inc a
+ ld b, a
+ ld a, [wListScrollOffset] ; index of top (visible) menu item within the list
+ add b
+ ld [wMenuItemToSwap], a ; ID of item chosen for swapping (counts from 1)
+ ld c, 20
+ call DelayFrames
+ jp DisplayListMenuIDLoop
+.swapItems
+ ld a, [wCurrentMenuItem]
+ inc a
+ ld b, a
+ ld a, [wListScrollOffset]
+ add b
+ ld b, a
+ ld a, [wMenuItemToSwap] ; ID of item chosen for swapping (counts from 1)
+ cp b ; is the currently selected item the same as the first item to swap?
+ jp z, DisplayListMenuIDLoop ; ignore attempts to swap an item with itself
+ dec a
+ ld [wMenuItemToSwap], a ; ID of item chosen for swapping (counts from 1)
+ ld c, 20
+ call DelayFrames
+ push hl
+ push de
+ ld hl, wListPointer
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ inc hl ; hl = beginning of list entries
+ ld d, h
+ ld e, l ; de = beginning of list entries
+ ld a, [wCurrentMenuItem]
+ ld b, a
+ ld a, [wListScrollOffset]
+ add b
+ add a
+ ld c, a
+ ld b, 0
+ add hl, bc ; hl = address of currently selected item entry
+ ld a, [wMenuItemToSwap] ; ID of item chosen for swapping (counts from 1)
+ add a
+ add e
+ ld e, a
+ jr nc, .noCarry
+ inc d
+.noCarry ; de = address of first item to swap
+ ld a, [de]
+ ld b, a
+ ld a, [hli]
+ cp b
+ jr z, .swapSameItemType
+.swapDifferentItems
+ ldh [hSwapItemID], a ; save second item ID
+ ld a, [hld]
+ ldh [hSwapItemQuantity], a ; save second item quantity
+ ld a, [de]
+ ld [hli], a ; put first item ID in second item slot
+ inc de
+ ld a, [de]
+ ld [hl], a ; put first item quantity in second item slot
+ ldh a, [hSwapItemQuantity]
+ ld [de], a ; put second item quantity in first item slot
+ dec de
+ ldh a, [hSwapItemID]
+ ld [de], a ; put second item ID in first item slot
+ xor a
+ ld [wMenuItemToSwap], a ; 0 means no item is currently being swapped
+ pop de
+ pop hl
+ jp DisplayListMenuIDLoop
+.swapSameItemType
+ inc de
+ ld a, [hl]
+ ld b, a
+ ld a, [de]
+ add b ; a = sum of both item quantities
+ cp 100 ; is the sum too big for one item slot?
+ jr c, .combineItemSlots
+; swap enough items from the first slot to max out the second slot if they can't be combined
+ sub 99
+ ld [de], a
+ ld a, 99
+ ld [hl], a
+ jr .done
+.combineItemSlots
+ ld [hl], a ; put the sum in the second item slot
+ ld hl, wListPointer
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ dec [hl] ; decrease the number of items
+ ld a, [hl]
+ ld [wListCount], a ; update number of items variable
+ cp 1
+ jr nz, .skipSettingMaxMenuItemID
+ ld [wMaxMenuItem], a ; if the number of items is only one now, update the max menu item ID
+.skipSettingMaxMenuItemID
+ dec de
+ ld h, d
+ ld l, e
+ inc hl
+ inc hl ; hl = address of item after first item to swap
+.moveItemsUpLoop ; erase the first item slot and move up all the following item slots to fill the gap
+ ld a, [hli]
+ ld [de], a
+ inc de
+ inc a ; reached the $ff terminator?
+ jr z, .afterMovingItemsUp
+ ld a, [hli]
+ ld [de], a
+ inc de
+ jr .moveItemsUpLoop
+.afterMovingItemsUp
+ xor a
+ ld [wListScrollOffset], a
+ ld [wCurrentMenuItem], a
+.done
+ xor a
+ ld [wMenuItemToSwap], a ; 0 means no item is currently being swapped
+ pop de
+ pop hl
+ jp DisplayListMenuIDLoop
diff --git a/engine/menus/text_box.asm b/engine/menus/text_box.asm
new file mode 100644
index 00000000..7a315af2
--- /dev/null
+++ b/engine/menus/text_box.asm
@@ -0,0 +1,533 @@
+; function to draw various text boxes
+DisplayTextBoxID_::
+ ld a, [wTextBoxID]
+ cp 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 $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
+ hlcoord 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
+
+INCLUDE "data/text_boxes.asm"
+
+DisplayMoneyBox:
+ ld hl, wd730
+ set 6, [hl]
+ ld a, MONEY_BOX_TEMPLATE
+ ld [wTextBoxID], a
+ call DisplayTextBoxID
+ hlcoord 13, 1
+ lb bc, 1, 6
+ call ClearScreenArea
+ hlcoord 12, 1
+ ld de, wPlayerMoney
+ ld c, $a3
+ call PrintBCDNumber
+ ld hl, wd730
+ res 6, [hl]
+ ret
+
+CurrencyString:
+ db " ¥@"
+
+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
+ xor a
+ ld [wTwoOptionMenuID], a
+ ld hl, wd730
+ res 6, [hl] ; turn on the printing delay
+ 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, 6
+.loop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ dec c
+ jr nz, .loop
+ push bc
+ ld bc, SCREEN_WIDTH - 6
+ add hl, bc
+ pop bc
+ ld c, $6
+ dec b
+ jr nz, .loop
+ ret
+
+TwoOptionMenu_RestoreScreenTiles:
+ ld de, wBuffer
+ lb bc, 5, 6
+.loop
+ ld a, [de]
+ inc de
+ ld [hli], a
+ dec c
+ jr nz, .loop
+ push bc
+ ld bc, SCREEN_WIDTH - 6
+ add hl, bc
+ pop bc
+ ld c, 6
+ dec b
+ jr nz, .loop
+ call UpdateSprites
+ ret
+
+INCLUDE "data/yes_no_menu_strings.asm"
+
+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
+ hlcoord 11, 11
+ lb bc, 5, 7
+ call TextBoxBorder
+ call UpdateSprites
+ ld a, 12
+ ldh [hFieldMoveMonMenuTopMenuItemX], a
+ hlcoord 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.
+ hlcoord 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.
+ hlcoord 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]
+ ldh [hFieldMoveMonMenuTopMenuItemX], a
+ hlcoord 0, 12
+ ld a, [wFieldMovesLeftmostXCoord]
+ inc a
+ ld e, a
+ ld d, 0
+ add hl, de
+ ld de, PokemonMenuEntries
+ jp PlaceString
+
+INCLUDE "data/moves/field_move_names.asm"
+
+PokemonMenuEntries:
+ db "STATS"
+ next "SWITCH"
+ next "CANCEL@"
+
+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
+
+INCLUDE "data/moves/field_moves.asm"