summaryrefslogtreecommitdiff
path: root/engine/menus
diff options
context:
space:
mode:
Diffstat (limited to 'engine/menus')
-rw-r--r--engine/menus/empty_sram.asm19
-rw-r--r--engine/menus/intro_menu.asm1133
-rw-r--r--engine/menus/main_menu.asm253
-rw-r--r--engine/menus/menu.asm675
-rw-r--r--engine/menus/menu_2.asm298
-rw-r--r--engine/menus/naming_screen.asm1377
-rw-r--r--engine/menus/save.asm1092
-rw-r--r--engine/menus/scrolling_menu.asm519
-rw-r--r--engine/menus/start_menu.asm541
-rw-r--r--engine/menus/trainer_card.asm624
10 files changed, 6531 insertions, 0 deletions
diff --git a/engine/menus/empty_sram.asm b/engine/menus/empty_sram.asm
new file mode 100644
index 00000000..264f0813
--- /dev/null
+++ b/engine/menus/empty_sram.asm
@@ -0,0 +1,19 @@
+EmptyAllSRAMBanks:
+ ld a, 0
+ call .EmptyBank
+ ld a, 1
+ call .EmptyBank
+ ld a, 2
+ call .EmptyBank
+ ld a, 3
+ call .EmptyBank
+ ret
+
+.EmptyBank:
+ call OpenSRAM
+ ld hl, SRAM_Begin
+ ld bc, SRAM_End - SRAM_Begin
+ xor a
+ call ByteFill
+ call CloseSRAM
+ ret
diff --git a/engine/menus/intro_menu.asm b/engine/menus/intro_menu.asm
new file mode 100644
index 00000000..c85b56f6
--- /dev/null
+++ b/engine/menus/intro_menu.asm
@@ -0,0 +1,1133 @@
+MainMenu_NewGame:
+NewGame:
+ xor a
+ ld [wDebugFlags], a
+ call ResetWRAM
+ call ClearTilemapEtc
+ call OakSpeech
+ call InitializeWorld
+
+ ld a, SPAWN_HOME
+ ld [wDefaultSpawnpoint], a
+
+ ld a, MAPSETUP_WARP
+ ldh [hMapEntryMethod], a
+ jp FinishContinueFunction
+
+ResetWRAM:
+ xor a
+ ldh [hBGMapMode], a
+ call _ResetWRAM
+ ret
+
+_ResetWRAM:
+ ld hl, wVirtualOAM
+ ld bc, wOptions - wVirtualOAM
+ xor a
+ call ByteFill
+
+ ld hl, wGameData
+ ld bc, wGameDataEnd - wGameData
+ xor a
+ call ByteFill
+
+ ldh a, [rLY]
+ ldh [hUnusedBackup], a
+ call DelayFrame
+ ldh a, [hRandomSub]
+ ld [wPlayerID], a
+
+ ldh a, [rLY]
+ ldh [hUnusedBackup], a
+ call DelayFrame
+ ldh a, [hRandomAdd]
+ ld [wPlayerID + 1], a
+
+ ld hl, wPartyCount
+ call .InitList
+
+ xor a
+ ld [wCurBox], a
+ ld [wSavedAtLeastOnce], a
+
+ call SetDefaultBoxNames
+
+ ld a, BANK(sBoxCount)
+ call OpenSRAM
+ ld hl, sBoxCount
+ call .InitList
+ call CloseSRAM
+
+ ld hl, wNumItems
+ call .InitList
+
+ ld hl, wNumKeyItems
+ call .InitList
+
+ ld hl, wNumBalls
+ call .InitList
+
+ ld hl, wNumPCItems
+ call .InitList
+
+ xor a
+ ld [wRoamMon1Species], a
+ ld [wRoamMon2Species], a
+ ld [wRoamMon3Species], a
+ ld a, -1
+ ld [wRoamMon1MapGroup], a
+ ld [wRoamMon2MapGroup], a
+ ld [wRoamMon3MapGroup], a
+ ld [wRoamMon1MapNumber], a
+ ld [wRoamMon2MapNumber], a
+ ld [wRoamMon3MapNumber], a
+
+ ld a, BANK(sMysteryGiftItem)
+ call OpenSRAM
+ ld hl, sMysteryGiftItem
+ xor a
+ ld [hli], a
+ dec a
+ ld [hl], a
+ call CloseSRAM
+
+ call LoadOrRegenerateLuckyIDNumber
+ call InitializeMagikarpHouse
+
+ xor a
+ ld [wMonType], a
+
+ ld [wJohtoBadges], a
+ ld [wKantoBadges], a
+
+ ld [wCoins], a
+ ld [wCoins + 1], a
+
+if START_MONEY >= $10000
+ ld a, HIGH(START_MONEY >> 8)
+endc
+ ld [wMoney], a
+ ld a, HIGH(START_MONEY) ; mid
+ ld [wMoney + 1], a
+ ld a, LOW(START_MONEY)
+ ld [wMoney + 2], a
+
+ xor a
+ ld [wWhichMomItem], a
+
+ ld hl, wMomItemTriggerBalance
+ ld [hl], HIGH(MOM_MONEY >> 8)
+ inc hl
+ ld [hl], HIGH(MOM_MONEY) ; mid
+ inc hl
+ ld [hl], LOW(MOM_MONEY)
+
+ call InitializeNPCNames
+
+ farcall InitDecorations
+
+ farcall DeletePartyMonMail
+
+ call ResetGameTime
+ ret
+
+.InitList:
+; Loads 0 in the count and -1 in the first item or mon slot.
+ xor a
+ ld [hli], a
+ dec a
+ ld [hl], a
+ ret
+
+SetDefaultBoxNames:
+ ld hl, wBoxNames
+ ld c, 0
+.loop
+ push hl
+ ld de, .Box
+ call CopyName2
+ dec hl
+ ld a, c
+ inc a
+ cp 10
+ jr c, .less
+ sub 10
+ ld [hl], "1"
+ inc hl
+
+.less
+ add "0"
+ ld [hli], a
+ ld [hl], "@"
+ pop hl
+ ld de, 9
+ add hl, de
+ inc c
+ ld a, c
+ cp NUM_BOXES
+ jr c, .loop
+ ret
+
+.Box:
+ db "BOX@"
+
+InitializeMagikarpHouse:
+ ld hl, wBestMagikarpLengthFeet
+ ld a, $3
+ ld [hli], a
+ ld a, $6
+ ld [hli], a
+ ld de, .Ralph
+ call CopyName2
+ ret
+
+.Ralph:
+ db "RALPH@"
+
+InitializeNPCNames:
+ ld hl, .Rival
+ ld de, wRivalName
+ call .Copy
+
+ ld hl, .Mom
+ ld de, wMomsName
+ call .Copy
+
+ ld hl, .Red
+ ld de, wRedsName
+ call .Copy
+
+ ld hl, .Green
+ ld de, wGreensName
+
+.Copy:
+ ld bc, NAME_LENGTH
+ call CopyBytes
+ ret
+
+.Rival: db "???@"
+.Red: db "RED@"
+.Green: db "GREEN@"
+.Mom: db "MOM@"
+
+InitializeWorld:
+ call ShrinkPlayer
+ farcall SpawnPlayer
+ farcall _InitializeStartDay
+ ret
+
+LoadOrRegenerateLuckyIDNumber:
+ ld a, BANK(sLuckyIDNumber)
+ call OpenSRAM
+ ld a, [wCurDay]
+ inc a
+ ld b, a
+ ld a, [sLuckyNumberDay]
+ cp b
+ ld a, [sLuckyIDNumber + 1]
+ ld c, a
+ ld a, [sLuckyIDNumber]
+ jr z, .skip
+ ld a, b
+ ld [sLuckyNumberDay], a
+ call Random
+ ld c, a
+ call Random
+
+.skip
+ ld [wLuckyIDNumber], a
+ ld [sLuckyIDNumber], a
+ ld a, c
+ ld [wLuckyIDNumber + 1], a
+ ld [sLuckyIDNumber + 1], a
+ jp CloseSRAM
+
+MainMenu_Continue:
+Continue:
+ farcall TryLoadSaveFile
+ jr c, .FailToLoad
+ call LoadStandardMenuHeader
+ call DisplaySaveInfoOnContinue
+ ld a, $1
+ ldh [hBGMapMode], a
+ ld c, 20
+ call DelayFrames
+ call ConfirmContinue
+ jr nc, .Check1Pass
+ call CloseWindow
+ jr .FailToLoad
+
+.Check1Pass:
+ call Continue_CheckRTC_RestartClock
+ jr nc, .Check2Pass
+ call CloseWindow
+ jr .FailToLoad
+
+.Check2Pass:
+ ld a, $8
+ ld [wMusicFade], a
+ ld a, LOW(MUSIC_NONE)
+ ld [wMusicFadeID], a
+ ld a, HIGH(MUSIC_NONE)
+ ld [wMusicFadeID + 1], a
+ call ClearBGPalettes
+ call CloseWindow
+ call ClearTilemap
+ ld c, 20
+ call DelayFrames
+ farcall JumpRoamMons
+ farcall MysteryGift_CopyReceivedDecosToPC ; Mystery Gift
+ farcall ClockContinue
+ ld a, [wSpawnAfterChampion]
+ cp SPAWN_LANCE
+ jr z, .SpawnAfterE4
+ ld a, MAPSETUP_CONTINUE
+ ldh [hMapEntryMethod], a
+ jp FinishContinueFunction
+
+.FailToLoad:
+ ret
+
+.SpawnAfterE4:
+ ld a, SPAWN_NEW_BARK
+ ld [wDefaultSpawnpoint], a
+ call PostCreditsSpawn
+ jp FinishContinueFunction
+
+SpawnAfterRed:
+ ld a, SPAWN_MT_SILVER
+ ld [wDefaultSpawnpoint], a
+
+PostCreditsSpawn:
+ xor a
+ ld [wSpawnAfterChampion], a
+ ld a, MAPSETUP_WARP
+ ldh [hMapEntryMethod], a
+ ret
+
+ConfirmContinue:
+.loop
+ call DelayFrame
+ call GetJoypad
+ ld hl, hJoyPressed
+ bit A_BUTTON_F, [hl]
+ jr nz, .PressA
+ bit B_BUTTON_F, [hl]
+ jr z, .loop
+ scf
+ ret
+
+.PressA:
+ ret
+
+Continue_CheckRTC_RestartClock:
+ call CheckRTCStatus
+ and %10000000 ; Day count exceeded 16383
+ jr z, .pass
+ farcall RestartClock
+ ld a, c
+ and a
+ jr z, .pass
+ scf
+ ret
+
+.pass
+ xor a
+ ret
+
+FinishContinueFunction:
+.loop
+ xor a
+ ld [wDontPlayMapMusicOnReload], a
+ ld hl, wGameTimerPause
+ set GAMETIMERPAUSE_TIMER_PAUSED_F, [hl]
+ farcall OverworldLoop
+ ld a, [wSpawnAfterChampion]
+ cp SPAWN_RED
+ jr z, .AfterRed
+ jp Reset
+
+.AfterRed:
+ call SpawnAfterRed
+ jr .loop
+
+DisplaySaveInfoOnContinue:
+ call CheckRTCStatus
+ and %10000000
+ jr z, .clock_ok
+ lb de, 4, 8
+ call DisplayContinueDataWithRTCError
+ ret
+
+.clock_ok
+ lb de, 4, 8
+ call DisplayNormalContinueData
+ ret
+
+DisplayNormalContinueData:
+ call Continue_LoadMenuHeader
+ call Continue_DisplayBadgesDex
+ call Continue_PrintGameTime
+ call LoadFontsExtra
+ call UpdateSprites
+ ret
+
+DisplayContinueDataWithRTCError:
+ call Continue_LoadMenuHeader
+ call Continue_DisplayBadgesDex
+ call Continue_UnknownGameTime
+ call LoadFontsExtra
+ call UpdateSprites
+ ret
+
+Continue_LoadMenuHeader:
+ xor a
+ ldh [hBGMapMode], a
+ ld hl, .MenuHeader_Dex
+ ld a, [wStatusFlags]
+ bit STATUSFLAGS_POKEDEX_F, a
+ jr nz, .show_menu
+ ld hl, .MenuHeader_NoDex
+
+.show_menu
+ call _OffsetMenuHeader
+ call MenuBox
+ call PlaceVerticalMenuItems
+ ret
+
+.MenuHeader_Dex:
+ db MENU_BACKUP_TILES ; flags
+ menu_coords 0, 0, 15, 9
+ dw .MenuData_Dex
+ db 1 ; default option
+
+.MenuData_Dex:
+ db 0 ; flags
+ db 4 ; items
+ db "PLAYER <PLAYER>@"
+ db "BADGES@"
+ db "#DEX@"
+ db "TIME@"
+
+.MenuHeader_NoDex:
+ db MENU_BACKUP_TILES ; flags
+ menu_coords 0, 0, 15, 9
+ dw .MenuData_NoDex
+ db 1 ; default option
+
+.MenuData_NoDex:
+ db 0 ; flags
+ db 4 ; items
+ db "PLAYER <PLAYER>@"
+ db "BADGES@"
+ db " @"
+ db "TIME@"
+
+Continue_DisplayBadgesDex:
+ call MenuBoxCoord2Tile
+ push hl
+ decoord 13, 4, 0
+ add hl, de
+ call Continue_DisplayBadgeCount
+ pop hl
+ push hl
+ decoord 12, 6, 0
+ add hl, de
+ call Continue_DisplayPokedexNumCaught
+ pop hl
+ ret
+
+Continue_PrintGameTime:
+ decoord 9, 8, 0
+ add hl, de
+ call Continue_DisplayGameTime
+ ret
+
+Continue_UnknownGameTime:
+ decoord 9, 8, 0
+ add hl, de
+ ld de, .three_question_marks
+ call PlaceString
+ ret
+
+.three_question_marks
+ db " ???@"
+
+Continue_DisplayBadgeCount:
+ push hl
+ ld hl, wJohtoBadges
+ ld b, 2
+ call CountSetBits
+ pop hl
+ ld de, wNumSetBits
+ lb bc, 1, 2
+ jp PrintNum
+
+Continue_DisplayPokedexNumCaught:
+ ld a, [wStatusFlags]
+ bit STATUSFLAGS_POKEDEX_F, a
+ ret z
+ push hl
+ ld hl, wPokedexCaught
+if NUM_POKEMON % 8
+ ld b, NUM_POKEMON / 8 + 1
+else
+ ld b, NUM_POKEMON / 8
+endc
+ call CountSetBits
+ pop hl
+ ld de, wNumSetBits
+ lb bc, 1, 3
+ jp PrintNum
+
+Continue_DisplayGameTime:
+ ld de, wGameTimeHours
+ lb bc, 2, 3
+ call PrintNum
+ ld [hl], "<COLON>"
+ inc hl
+ ld de, wGameTimeMinutes
+ lb bc, PRINTNUM_LEADINGZEROS | 1, 2
+ jp PrintNum
+
+OakSpeech:
+ farcall InitClock
+ call RotateFourPalettesLeft
+ call ClearTilemap
+
+ ld de, MUSIC_ROUTE_30
+ call PlayMusic
+
+ call RotateFourPalettesRight
+ call RotateThreePalettesRight
+ xor a
+ ld [wCurPartySpecies], a
+ ld a, POKEMON_PROF
+ ld [wTrainerClass], a
+ call Intro_PrepTrainerPic
+
+ ld b, SCGB_TRAINER_OR_MON_FRONTPIC_PALS
+ call GetSGBLayout
+ call Intro_RotatePalettesLeftFrontpic
+
+ ld hl, OakText1
+ call PrintText
+ call RotateThreePalettesRight
+ call ClearTilemap
+
+ ld a, MARILL
+ ld [wCurSpecies], a
+ ld [wCurPartySpecies], a
+ call GetBaseData
+
+ hlcoord 6, 4
+ hlcoord 6, 4 ; TriHard
+ call PrepMonFrontpic
+
+ xor a
+ ld [wTempMonDVs], a
+ ld [wTempMonDVs + 1], a
+
+ ld b, SCGB_TRAINER_OR_MON_FRONTPIC_PALS
+ call GetSGBLayout
+ call Intro_WipeInFrontpic
+
+ ld hl, OakText2
+ call PrintText
+ ld hl, OakText4
+ call PrintText
+ call RotateThreePalettesRight
+ call ClearTilemap
+
+ xor a
+ ld [wCurPartySpecies], a
+ ld a, POKEMON_PROF
+ ld [wTrainerClass], a
+ call Intro_PrepTrainerPic
+
+ ld b, SCGB_TRAINER_OR_MON_FRONTPIC_PALS
+ call GetSGBLayout
+ call Intro_RotatePalettesLeftFrontpic
+
+ ld hl, OakText5
+ call PrintText
+ call RotateThreePalettesRight
+ call ClearTilemap
+
+ xor a
+ ld [wCurPartySpecies], a
+ ld a, CAL
+ ld [wTrainerClass], a
+ call Intro_PrepTrainerPic
+
+ ld b, SCGB_TRAINER_OR_MON_FRONTPIC_PALS
+ call GetSGBLayout
+ call Intro_RotatePalettesLeftFrontpic
+
+ ld hl, OakText6
+ call PrintText
+ call NamePlayer
+ ld hl, OakText7
+ call PrintText
+ ret
+
+OakText1:
+ text_far _OakText1
+ text_end
+
+OakText2:
+ text_far _OakText2
+ text_asm
+ ld a, MARILL
+ call PlayMonCry
+ call WaitSFX
+ ld hl, OakText3
+ ret
+
+OakText3:
+ text_far _OakText3
+ text_end
+
+OakText4:
+ text_far _OakText4
+ text_end
+
+OakText5:
+ text_far _OakText5
+ text_end
+
+OakText6:
+ text_far _OakText6
+ text_end
+
+OakText7:
+ text_far _OakText7
+ text_end
+
+NamePlayer:
+ call MovePlayerPicRight
+ ld hl, NameMenuHeader
+ call ShowPlayerNamingChoices
+ ld a, [wMenuCursorY]
+ dec a
+ jr z, .NewName
+ ld de, wPlayerName
+ call StorePlayerName
+ farcall ApplyMonOrTrainerPals
+ call MovePlayerPicLeft
+ ret
+
+.NewName:
+ ld b, NAME_PLAYER
+ ld de, wPlayerName
+ farcall NamingScreen
+
+ call RotateThreePalettesRight
+ call ClearTilemap
+
+ call LoadFontsExtra
+ call WaitBGMap
+
+ xor a
+ ld [wCurPartySpecies], a
+ ld a, CAL
+ ld [wTrainerClass], a
+ call Intro_PrepTrainerPic
+
+ ld b, SCGB_TRAINER_OR_MON_FRONTPIC_PALS
+ call GetSGBLayout
+ call RotateThreePalettesLeft
+
+ ld hl, wPlayerName
+ ld de, PlayerNameArray
+ call InitName
+ ret
+
+INCLUDE "data/player_names.asm"
+
+ShowPlayerNamingChoices:
+ call LoadMenuHeader
+ call VerticalMenu
+ ld a, [wMenuCursorY]
+ dec a
+ call CopyNameFromMenu
+ call CloseWindow
+ ret
+
+StorePlayerName:
+ ld hl, wStringBuffer2
+ ld bc, NAME_LENGTH
+ call CopyBytes
+ ret
+
+ShrinkPlayer:
+ ldh a, [hROMBank]
+ push af
+
+ ld a, 32 ; fade time
+ ld [wMusicFade], a
+ ld de, MUSIC_NONE
+ ld a, e
+ ld [wMusicFadeID], a
+ ld a, d
+ ld [wMusicFadeID + 1], a
+
+ ld de, SFX_ESCAPE_ROPE
+ call PlaySFX
+ pop af
+ rst Bankswitch
+
+ ld c, 8
+ call DelayFrames
+
+ ld hl, Shrink1Pic
+ ld b, BANK(Shrink1Pic)
+ call ShrinkFrame
+
+ ld c, 8
+ call DelayFrames
+
+ ld hl, Shrink2Pic
+ ld b, BANK(Shrink2Pic)
+ call ShrinkFrame
+
+ ld c, 8
+ call DelayFrames
+
+ hlcoord 6, 5
+ ld b, 7
+ ld c, 7
+ call ClearBox
+
+ ld c, 3
+ call DelayFrames
+
+ call Intro_PlaceChrisSprite
+ call LoadFontsExtra
+
+ ld c, 50
+ call DelayFrames
+
+ call RotateThreePalettesRight
+ call ClearTilemap
+ ret
+
+MovePlayerPicRight:
+ hlcoord 6, 4
+ ld de, $1
+ jr MovePlayerPic
+
+MovePlayerPicLeft:
+ hlcoord 13, 4
+ ld de, -1
+MovePlayerPic:
+ ld c, $8
+.loop
+ push bc
+ push hl
+ push de
+ xor a
+ ldh [hBGMapMode], a
+ lb bc, 7, 7
+ predef PlaceGraphic
+ xor a
+ ldh [hBGMapThird], a
+ call WaitBGMap
+ call DelayFrame
+ pop de
+ pop hl
+ add hl, de
+ pop bc
+ dec c
+ jr nz, .loop
+ ret
+
+Intro_RotatePalettesLeftFrontpic:
+ ld hl, IntroFadePalettes
+ ld b, IntroFadePalettes.End - IntroFadePalettes
+.loop
+ ld a, [hli]
+ call DmgToCgbBGPals
+ ld c, 10
+ call DelayFrames
+ dec b
+ jr nz, .loop
+ ret
+
+IntroFadePalettes:
+ dc 1, 1, 1, 0
+ dc 2, 2, 2, 0
+ dc 3, 3, 3, 0
+ dc 3, 3, 2, 0
+ dc 3, 3, 1, 0
+ dc 3, 2, 1, 0
+.End
+
+Intro_WipeInFrontpic:
+ ld a, $77
+ ldh [hWX], a
+ call DelayFrame
+ ld a, %11100100
+ call DmgToCgbBGPals
+.loop
+ call DelayFrame
+ ldh a, [hWX]
+ sub $8
+ cp -1
+ ret z
+ ldh [hWX], a
+ jr .loop
+
+Intro_PrepTrainerPic:
+ ld de, vTiles2
+ farcall GetTrainerPic
+ xor a
+ ldh [hGraphicStartTile], a
+ hlcoord 6, 4
+ lb bc, 7, 7
+ predef PlaceGraphic
+ ret
+
+ShrinkFrame:
+ ld de, vTiles2
+ ld c, 7 * 7
+ predef DecompressGet2bpp
+ xor a
+ ldh [hGraphicStartTile], a
+ hlcoord 6, 4
+ lb bc, 7, 7
+ predef PlaceGraphic
+ ret
+
+Intro_PlaceChrisSprite:
+ ld de, ChrisSpriteGFX
+ lb bc, BANK(ChrisSpriteGFX), 12
+ ld hl, vTiles0
+ call Request2bpp
+
+ ld hl, wVirtualOAMSprite00
+ ld de, .sprites
+ ld a, [de]
+ inc de
+
+ ld c, a
+.loop
+ ld a, [de]
+ inc de
+ ld [hli], a ; y
+ ld a, [de]
+ inc de
+ ld [hli], a ; x
+ ld a, [de]
+ inc de
+ ld [hli], a ; tile id
+ xor a ; PAL_OW_RED
+ ld [hli], a
+ dec c
+ jr nz, .loop
+ ret
+
+.sprites
+ db 4
+ ; y pxl, x pxl, tile offset
+ db 9 * 8 + 4, 9 * 8, 0
+ db 9 * 8 + 4, 10 * 8, 1
+ db 10 * 8 + 4, 9 * 8, 2
+ db 10 * 8 + 4, 10 * 8, 3
+
+IntroSequence:
+ callfar Copyright_GFPresents
+ jr c, StartTitleScreen
+ callfar GoldSilverIntro
+
+ ; fallthrough
+
+StartTitleScreen:
+ call TitleScreen
+ call DelayFrame
+.loop
+ call RunTitleScreen
+ jr nc, .loop
+
+ call ClearSprites
+ call ClearBGPalettes
+
+ ld hl, rLCDC
+ res rLCDC_SPRITE_SIZE, [hl] ; 8x8
+ call ClearTilemap
+ xor a
+ ldh [hLCDCPointer], a
+ ld b, SCGB_DIPLOMA
+ call GetSGBLayout
+ call UpdateTimePals
+ ld a, [wIntroSceneFrameCounter]
+ cp $5
+ jr c, .ok
+ xor a
+.ok
+ ld e, a
+ ld d, 0
+ ld hl, .dw
+ add hl, de
+ add hl, de
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ jp hl
+
+.dw
+ dw MainMenu
+ dw DeleteSaveData
+ dw IntroSequence
+ dw IntroSequence
+ dw ResetClock
+
+INCLUDE "engine/movie/title.asm"
+
+RunTitleScreen:
+ call Function63fe
+ ld a, [wJumptableIndex]
+ bit 7, a
+ jr nz, .done_title
+ call TitleScreenScene
+ ld a, $1
+ ldh [hOAMUpdate], a
+ farcall PlaySpriteAnimations
+ xor a
+ ldh [hOAMUpdate], a
+ call Function64b1
+ call DelayFrame
+ and a
+ ret
+
+.done_title
+ scf
+ ret
+
+Function63fe:
+IF DEF(_GOLD)
+ ldh a, [hVBlankCounter]
+ and $7
+ ret nz
+ENDC
+ ld hl, wLYOverrides + $5f
+ ld a, [hl]
+ dec a
+ ld bc, 2 * SCREEN_WIDTH
+ call ByteFill
+ ret
+
+TitleScreenScene:
+ ld e, a
+ ld d, 0
+ ld hl, .scenes
+ add hl, de
+ add hl, de
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ jp hl
+
+.scenes
+ dw TitleScreenTimer
+ dw TitleScreenMain
+ dw TitleScreenEnd
+
+.Unreferenced_NextScene:
+ ld hl, wJumptableIndex
+ inc [hl]
+ ret
+
+TitleScreenTimer:
+; Next scene
+ ld hl, wJumptableIndex
+ inc [hl]
+
+; Start a timer
+ ld hl, wTitleScreenTimer
+IF DEF(_GOLD)
+ ld de, 84 * 60 + 16
+ELIF DEF(_SILVER)
+ ld de, 73 * 60 + 36
+ENDC
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ret
+
+TitleScreenMain:
+; Run the timer down.
+ ld hl, wTitleScreenTimer
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ld a, e
+ or d
+ jr z, .end
+
+ dec de
+ ld [hl], d
+ dec hl
+ ld [hl], e
+
+; Save data can be deleted by pressing Up + B + Select.
+ call GetJoypad
+ ld hl, hJoyDown
+ ld a, [hl]
+ and D_UP + B_BUTTON + SELECT
+ cp D_UP + B_BUTTON + SELECT
+ jr z, .delete_save_data
+
+; Clock can be reset by pressing Down + B + Select.
+ ld a, [hl]
+ and D_DOWN + B_BUTTON + SELECT
+ cp D_DOWN + B_BUTTON + SELECT
+ jr z, .clock_reset
+ ld a, [hl]
+ and START | A_BUTTON
+ jr nz, .incave
+ ret
+
+.incave
+ ld a, 0
+ jr .done
+
+.delete_save_data
+ ld a, 1
+
+.done
+ ld [wIntroSceneFrameCounter], a
+
+; Return to the intro sequence.
+ ld hl, wJumptableIndex
+ set 7, [hl]
+ ret
+
+.end
+; Next scene
+ ld hl, wJumptableIndex
+ inc [hl]
+
+; Fade out the title screen music
+ xor a ; MUSIC_NONE
+ ld [wMusicFadeID], a
+ ld [wMusicFadeID + 1], a
+ ld hl, wMusicFade
+ ld [hl], 8 ; 1 second
+
+ ld hl, wTitleScreenTimer
+ inc [hl]
+ ret
+
+.clock_reset
+ ld a, 4
+ ld [wIntroSceneFrameCounter], a
+
+; Return to the intro sequence.
+ ld hl, wJumptableIndex
+ set 7, [hl]
+ ret
+
+TitleScreenEnd:
+; Wait until the music is done fading.
+
+ ld hl, wTitleScreenTimer
+ inc [hl]
+
+ ld a, [wMusicFade]
+ and a
+ ret nz
+
+ ld a, 2
+ ld [wIntroSceneFrameCounter], a
+
+; Back to the intro.
+ ld hl, wJumptableIndex
+ set 7, [hl]
+ ret
+
+DeleteSaveData:
+ farcall _DeleteSaveData
+ jp Init
+
+ResetClock:
+ farcall _ResetClock
+ jp Init
+
+Function64b1:
+ ; If bit 0 or 1 of [wTitleScreenTimer] is set, we don't need to be here.
+ ld a, [wTitleScreenTimer]
+ and %00000011
+ ret nz
+IF DEF(_GOLD)
+ ld bc, wSpriteAnim10
+ ld hl, SPRITEANIMSTRUCT_FRAME
+ add hl, bc
+ ld l, [hl]
+ ld h, 0
+ add hl, hl
+ add hl, hl
+ ld de, .Data_64e0
+ add hl, de
+ ; If bit 2 of [wTitleScreenTimer] is set, get the second dw; else, get the first dw
+ ld a, [wTitleScreenTimer]
+ and %00000100
+ srl a
+ srl a
+ ld e, a
+ ld d, 0
+ add hl, de
+ add hl, de
+ ld a, [hli]
+ and a
+ ret z
+ ld e, a
+ ld d, [hl]
+ELIF DEF(_SILVER)
+ depixel 15, 11, 4, 0
+ENDC
+ ld a, SPRITE_ANIM_INDEX_GS_TITLE_TRAIL
+ call InitSpriteAnimStruct
+ ret
+
+IF DEF(_GOLD)
+.Data_64e0:
+; frame 0 y, x; frame 1 y, x
+ db 11 * 8 + 4, 10 * 8, 0 * 8, 0 * 8
+ db 11 * 8 + 4, 13 * 8, 11 * 8 + 4, 11 * 8
+ db 11 * 8 + 4, 13 * 8, 11 * 8 + 4, 15 * 8
+ db 11 * 8 + 4, 17 * 8, 11 * 8 + 4, 15 * 8
+ db 0 * 8, 0 * 8, 11 * 8 + 4, 15 * 8
+ db 0 * 8, 0 * 8, 11 * 8 + 4, 11 * 8
+ENDC
+
+Copyright:
+ call ClearTilemap
+ call LoadFontsExtra
+ ld de, CopyrightGFX
+ ld hl, vTiles2 tile $60
+ lb bc, BANK(CopyrightGFX), 30
+ call Request2bpp
+ hlcoord 2, 7
+ ld de, CopyrightString
+ jp PlaceString
+
+CopyrightString:
+ ; ©1995-2000 Nintendo
+ db $60, $61, $62, $63, $7a, $7b, $7c, $7d
+ db $65, $66, $67, $68, $69, $6a
+
+ ; ©1995-2000 Creatures inc.
+ next $60, $61, $62, $63, $7a, $7b, $7c, $7d
+ db $6b, $6c, $6d, $6e, $6f, $70, $71, $72
+
+ ; ©1995-2000 GAME FREAK inc.
+ next $60, $61, $62, $63, $7a, $7b, $7c, $7d
+ db $73, $74, $75, $76, $77, $78, $79, $71, $72
+
+ db "@"
+
+GameInit::
+ call ClearWindowData
+ farcall TryLoadSaveData
+ jp IntroSequence
diff --git a/engine/menus/main_menu.asm b/engine/menus/main_menu.asm
new file mode 100644
index 00000000..4e9113a8
--- /dev/null
+++ b/engine/menus/main_menu.asm
@@ -0,0 +1,253 @@
+MainMenu:
+ ld de, MUSIC_NONE
+ call PlayMusic
+ call DelayFrame
+ ld de, MUSIC_MAIN_MENU
+ ld a, e
+ ld [wMapMusic], a
+ call PlayMusic
+.loop
+ xor a
+ ld [wDisableTextAcceleration], a
+ call ClearTilemapEtc
+ ld b, SCGB_DIPLOMA
+ call GetSGBLayout
+ ld hl, wGameTimerPause
+ res GAMETIMERPAUSE_TIMER_PAUSED_F, [hl]
+ call MainMenu_GetWhichMenu
+ ld [wWhichIndexSet], a
+ call MainMenu_PrintCurrentTimeAndDay
+ ld hl, .MenuHeader
+ call LoadMenuHeader
+ call MainMenuJoypadLoop
+ call CloseWindow
+ jr c, .quit
+ call ClearTilemap
+ ld a, [wMenuSelection]
+ ld hl, .Jumptable
+ rst JumpTable
+ jr .loop
+
+.quit
+ jp StartTitleScreen
+
+.MenuHeader:
+ db MENU_BACKUP_TILES ; flags
+ menu_coords 0, 0, 14, 7
+ dw .MenuData
+ db 1 ; default option
+
+.MenuData:
+ db STATICMENU_CURSOR ; flags
+ db 0 ; items
+ dw MainMenuItems
+ dw PlaceMenuStrings
+ dw .Strings
+
+.Strings:
+ db "CONTINUE@"
+ db "NEW GAME@"
+ db "OPTION@"
+ db "MYSTERY GIFT@"
+
+.Jumptable:
+ dw MainMenu_Continue
+ dw MainMenu_NewGame
+ dw MainMenu_Options
+ dw MainMenu_MysteryGift
+
+CONTINUE EQU 0
+NEW_GAME EQU 1
+OPTION EQU 2
+MYSTERY_GIFT EQU 3
+
+MainMenuItems:
+
+NewGameMenu:
+ db 2
+ db NEW_GAME
+ db OPTION
+ db -1
+
+ContinueMenu:
+ db 3
+ db CONTINUE
+ db NEW_GAME
+ db OPTION
+ db -1
+
+MysteryMenu:
+ db 4
+ db CONTINUE
+ db NEW_GAME
+ db OPTION
+ db MYSTERY_GIFT
+ db -1
+
+MainMenu_GetWhichMenu:
+ nop
+ nop
+ nop
+ ld a, [wSaveFileExists]
+ and a
+ jr nz, .next
+ ld a, $0 ; New Game
+ ret
+
+.next
+ ldh a, [hCGB]
+ cp $1
+ ld a, $1
+ ret nz
+ ld a, BANK(sNumDailyMysteryGiftPartnerIDs)
+ call OpenSRAM
+ ld a, [sNumDailyMysteryGiftPartnerIDs]
+ cp -1
+ call CloseSRAM
+ ld a, $1 ; Continue
+ ret z
+ ld a, $2 ; New Game
+ ret
+
+MainMenuJoypadLoop:
+ call SetUpMenu
+.loop
+ call MainMenu_PrintCurrentTimeAndDay
+ call GetScrollingMenuJoypad
+ ld a, [wMenuJoypad]
+ cp B_BUTTON
+ jr z, .b_button
+ cp A_BUTTON
+ jr z, .a_button
+ jr .loop
+
+.a_button
+ call PlayClickSFX
+ and a
+ ret
+
+.b_button
+ scf
+ ret
+
+MainMenu_PrintCurrentTimeAndDay:
+ ld a, [wSaveFileExists]
+ and a
+ ret z
+ xor a
+ ldh [hBGMapMode], a
+ call .PlaceBox
+ ld hl, wOptions
+ ld a, [hl]
+ push af
+ set NO_TEXT_SCROLL, [hl]
+ call .PlaceTime
+ pop af
+ ld [wOptions], a
+ ld a, $1
+ ldh [hBGMapMode], a
+ ret
+
+.PlaceBox:
+ call CheckRTCStatus
+ and $80
+ jr nz, .TimeFail
+ hlcoord 0, 12
+ ld b, 4
+ ld c, 13
+ call Textbox
+ ret
+
+.TimeFail:
+ call SpeechTextbox
+ ret
+
+.PlaceTime:
+ ld a, [wSaveFileExists]
+ and a
+ ret z
+ call CheckRTCStatus
+ and %10000000 ; Day count exceeded 16383
+ jp nz, .PrintTimeNotSet
+ call UpdateTime
+ hlcoord 1, 13
+ lb bc, 4, 13
+ call ClearBox
+ call GetWeekday
+ ld b, a
+ decoord 1, 14
+ call .PlaceCurrentDay
+ decoord 4, 16
+ ldh a, [hHours]
+ ld c, a
+ farcall PrintHour
+ ld [hl], ":"
+ inc hl
+ ld de, hMinutes
+ lb bc, PRINTNUM_LEADINGZEROS | 1, 2
+ call PrintNum
+ ret
+
+.min
+; unused
+ db "min.@"
+
+.PrintTimeNotSet:
+ hlcoord 1, 14
+ ld de, .TimeNotSet
+ call PlaceString
+ ret
+
+.TimeNotSet:
+ db "TIME NOT SET@"
+
+.MainMenuTimeUnknownText:
+ text_far _MainMenuTimeUnknownText
+ text_end
+
+.PlaceCurrentDay:
+ push de
+ ld hl, .Days
+ ld a, b
+ call GetNthString
+ ld d, h
+ ld e, l
+ pop hl
+ call PlaceString
+ ld h, b
+ ld l, c
+ ld de, .Day
+ call PlaceString
+ ret
+
+.Days:
+ db "SUN@"
+ db "MON@"
+ db "TUES@"
+ db "WEDNES@"
+ db "THURS@"
+ db "FRI@"
+ db "SATUR@"
+.Day:
+ db "DAY@"
+
+ClearTilemapEtc:
+ xor a
+ ldh [hMapAnims], a
+ call ClearTilemap
+ call LoadFontsExtra
+ call LoadStandardFont
+ call ClearWindowData
+ ret
+
+MainMenu_MysteryGift:
+MysteryGift:
+ call UpdateTime
+ farcall DoMysteryGiftIfDayHasPassed
+ farcall DoMysteryGift
+ ret
+
+MainMenu_Options:
+OptionsMenu:
+ farcall _OptionsMenu
+ ret
diff --git a/engine/menus/menu.asm b/engine/menus/menu.asm
new file mode 100644
index 00000000..b678d714
--- /dev/null
+++ b/engine/menus/menu.asm
@@ -0,0 +1,675 @@
+_2DMenu_::
+ xor a
+ ldh [hBGMapMode], a
+ call MenuBox
+ call Place2DMenuItemStrings
+ call UpdateSprites
+ call ApplyTilemap
+ call Init2DMenuCursorPosition
+ call StaticMenuJoypad
+ call MenuClickSound
+ ld a, [wMenuDataFlags]
+ bit 1, a
+ jr z, .skip
+ call GetMenuJoypad
+ bit SELECT_F, a
+ jr nz, .quit1
+
+.skip
+ ld a, [wMenuDataFlags]
+ bit 0, a
+ jr nz, .skip2
+ call GetMenuJoypad
+ bit B_BUTTON_F, a
+ jr nz, .quit2
+
+.skip2
+ ld a, [w2DMenuNumCols]
+ ld c, a
+ ld a, [wMenuCursorY]
+ dec a
+ call SimpleMultiply
+ ld c, a
+ ld a, [wMenuCursorX]
+ add c
+ ld [wMenuCursorBuffer], a
+ and a
+ ret
+
+.quit1
+ scf
+ ret
+
+.quit2
+ scf
+ ret
+
+Get2DMenuNumberOfColumns:
+ ld a, [wMenuData_2DMenuDimensions]
+ and $f
+ ret
+
+Get2DMenuNumberOfRows:
+ ld a, [wMenuData_2DMenuDimensions]
+ swap a
+ and $f
+ ret
+
+Place2DMenuItemStrings:
+ ld hl, wMenuData_2DMenuItemStringsAddr
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ call GetMenuTextStartCoord
+ call Coord2Tile
+ call Get2DMenuNumberOfRows
+ ld b, a
+.row
+ push bc
+ push hl
+ call Get2DMenuNumberOfColumns
+ ld c, a
+.col
+ push bc
+ ld a, [wMenuData_2DMenuItemStringsBank]
+ call Place2DMenuItemName
+ inc de
+ ld a, [wMenuData_2DMenuSpacing]
+ ld c, a
+ ld b, 0
+ add hl, bc
+ pop bc
+ dec c
+ jr nz, .col
+ pop hl
+ ld bc, 2 * SCREEN_WIDTH
+ add hl, bc
+ pop bc
+ dec b
+ jr nz, .row
+ ld hl, wMenuData_2DMenuFunctionAddr
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ or h
+ ret z
+ ld a, [wMenuData_2DMenuFunctionBank]
+ rst FarCall
+ ret
+
+Init2DMenuCursorPosition:
+ call GetMenuTextStartCoord
+ ld a, b
+ ld [w2DMenuCursorInitY], a
+ dec c
+ ld a, c
+ ld [w2DMenuCursorInitX], a
+ call Get2DMenuNumberOfRows
+ ld [w2DMenuNumRows], a
+ call Get2DMenuNumberOfColumns
+ ld [w2DMenuNumCols], a
+ call .InitFlags_a
+ call .InitFlags_b
+ call .InitFlags_c
+ ld a, [w2DMenuNumCols]
+ ld e, a
+ ld a, [wMenuCursorBuffer]
+ ld b, a
+ xor a
+ ld d, 0
+.loop
+ inc d
+ add e
+ cp b
+ jr c, .loop
+ sub e
+ ld c, a
+ ld a, b
+ sub c
+ and a
+ jr z, .reset1
+ cp e
+ jr z, .okay1
+ jr c, .okay1
+.reset1
+ ld a, 1
+.okay1
+ ld [wMenuCursorX], a
+ ld a, [w2DMenuNumRows]
+ ld e, a
+ ld a, d
+ and a
+ jr z, .reset2
+ cp e
+ jr z, .okay2
+ jr c, .okay2
+.reset2
+ ld a, 1
+.okay2
+ ld [wMenuCursorY], a
+ xor a
+ ld [wCursorOffCharacter], a
+ ld [wCursorCurrentTile], a
+ ld [wCursorCurrentTile + 1], a
+ ret
+
+.InitFlags_a:
+ xor a
+ ld hl, w2DMenuFlags1
+ ld [hli], a
+ ld [hld], a
+ ld a, [wMenuDataFlags]
+ bit 5, a
+ ret z
+ set 5, [hl]
+ set 4, [hl]
+ ret
+
+.InitFlags_b:
+ ld a, [wMenuData_2DMenuSpacing]
+ or $20
+ ld [w2DMenuCursorOffsets], a
+ ret
+
+.InitFlags_c:
+ ld hl, wMenuDataFlags
+ ld a, A_BUTTON
+ bit 0, [hl]
+ jr nz, .skip
+ or B_BUTTON
+.skip
+ bit 1, [hl]
+ jr z, .skip2
+ or SELECT
+.skip2
+ ld [wMenuJoypadFilter], a
+ ret
+
+_StaticMenuJoypad::
+ call Place2DMenuCursor
+_ScrollingMenuJoypad::
+ ld hl, w2DMenuFlags2
+ res 7, [hl]
+ ldh a, [hBGMapMode]
+ push af
+
+.menu_joypad_loop
+ call Move2DMenuCursor
+ ldh a, [hOAMUpdate]
+ push af
+ ld a, $1
+ ldh [hOAMUpdate], a
+ call WaitBGMap
+ pop af
+ ldh [hOAMUpdate], a
+ xor a
+ ldh [hBGMapMode], a
+
+.loopRTC
+ call UpdateTimeAndPals
+ call Menu_WasButtonPressed
+ jr c, .pressed
+ ld a, [w2DMenuFlags1]
+ bit 7, a
+ jp nz, .done
+ jr .loopRTC
+
+.pressed
+ call _2DMenuInterpretJoypad
+ jp c, .done
+ ld a, [w2DMenuFlags1]
+ bit 7, a
+ jr nz, .done
+ call GetMenuJoypad
+ ld b, a
+ ld a, [wMenuJoypadFilter]
+ and b
+ jp z, .menu_joypad_loop
+
+.done
+ pop af
+ ldh [hBGMapMode], a
+ call GetMenuJoypad
+ ret
+
+Menu_WasButtonPressed:
+ ld a, [w2DMenuFlags1]
+ bit 6, a
+ jr z, .skip_to_joypad
+ callfar PlaySpriteAnimationsAndDelayFrame
+
+.skip_to_joypad
+ call JoyTextDelay
+ call GetMenuJoypad
+ and a
+ ret z
+ scf
+ ret
+
+_2DMenuInterpretJoypad:
+ call GetMenuJoypad
+ bit A_BUTTON_F, a
+ jp nz, .a_b_start_select
+ bit B_BUTTON_F, a
+ jp nz, .a_b_start_select
+ bit SELECT_F, a
+ jp nz, .a_b_start_select
+ bit START_F, a
+ jp nz, .a_b_start_select
+ bit D_RIGHT_F, a
+ jr nz, .d_right
+ bit D_LEFT_F, a
+ jr nz, .d_left
+ bit D_UP_F, a
+ jr nz, .d_up
+ bit D_DOWN_F, a
+ jr nz, .d_down
+ and a
+ ret
+
+.set_bit_7
+ ld hl, w2DMenuFlags2
+ set 7, [hl]
+ scf
+ ret
+
+.d_down
+ ld hl, wMenuCursorY
+ ld a, [w2DMenuNumRows]
+ cp [hl]
+ jr z, .check_wrap_around_down
+ inc [hl]
+ xor a
+ ret
+
+.check_wrap_around_down
+ ld a, [w2DMenuFlags1]
+ bit 5, a
+ jr nz, .wrap_around_down
+ bit 3, a
+ jp nz, .set_bit_7
+ xor a
+ ret
+
+.wrap_around_down
+ ld [hl], $1
+ xor a
+ ret
+
+.d_up
+ ld hl, wMenuCursorY
+ ld a, [hl]
+ dec a
+ jr z, .check_wrap_around_up
+ ld [hl], a
+ xor a
+ ret
+
+.check_wrap_around_up
+ ld a, [w2DMenuFlags1]
+ bit 5, a
+ jr nz, .wrap_around_up
+ bit 2, a
+ jp nz, .set_bit_7
+ xor a
+ ret
+
+.wrap_around_up
+ ld a, [w2DMenuNumRows]
+ ld [hl], a
+ xor a
+ ret
+
+.d_left
+ ld hl, wMenuCursorX
+ ld a, [hl]
+ dec a
+ jr z, .check_wrap_around_left
+ ld [hl], a
+ xor a
+ ret
+
+.check_wrap_around_left
+ ld a, [w2DMenuFlags1]
+ bit 4, a
+ jr nz, .wrap_around_left
+ bit 1, a
+ jp nz, .set_bit_7
+ xor a
+ ret
+
+.wrap_around_left
+ ld a, [w2DMenuNumCols]
+ ld [hl], a
+ xor a
+ ret
+
+.d_right
+ ld hl, wMenuCursorX
+ ld a, [w2DMenuNumCols]
+ cp [hl]
+ jr z, .check_wrap_around_right
+ inc [hl]
+ xor a
+ ret
+
+.check_wrap_around_right
+ ld a, [w2DMenuFlags1]
+ bit 4, a
+ jr nz, .wrap_around_right
+ bit 0, a
+ jp nz, .set_bit_7
+ xor a
+ ret
+
+.wrap_around_right
+ ld [hl], $1
+ xor a
+ ret
+
+.a_b_start_select
+ xor a
+ ret
+
+Move2DMenuCursor:
+ ld hl, wCursorCurrentTile
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld a, [hl]
+ cp "▶"
+ jr nz, Place2DMenuCursor
+ ld a, [wCursorOffCharacter]
+ ld [hl], a
+Place2DMenuCursor:
+ ld a, [w2DMenuCursorInitY]
+ ld b, a
+ ld a, [w2DMenuCursorInitX]
+ ld c, a
+ call Coord2Tile
+ ld a, [w2DMenuCursorOffsets]
+ swap a
+ and $f
+ ld c, a
+ ld a, [wMenuCursorY]
+ ld b, a
+ xor a
+ dec b
+ jr z, .got_row
+.row_loop
+ add c
+ dec b
+ jr nz, .row_loop
+
+.got_row
+ ld c, SCREEN_WIDTH
+ call AddNTimes
+ ld a, [w2DMenuCursorOffsets]
+ and $f
+ ld c, a
+ ld a, [wMenuCursorX]
+ ld b, a
+ xor a
+ dec b
+ jr z, .got_col
+.col_loop
+ add c
+ dec b
+ jr nz, .col_loop
+
+.got_col
+ ld c, a
+ add hl, bc
+ ld a, [hl]
+ cp "▶"
+ jr z, .cursor_on
+ ld [wCursorOffCharacter], a
+ ld [hl], "▶"
+
+.cursor_on
+ ld a, l
+ ld [wCursorCurrentTile], a
+ ld a, h
+ ld [wCursorCurrentTile + 1], a
+ ret
+
+_PushWindow::
+ xor a ; BANK(sWindowStack)
+ call OpenSRAM
+
+ ld hl, wWindowStackPointer
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ push de
+ ld b, $10
+ ld hl, wMenuFlags
+.loop
+ ld a, [hli]
+ ld [de], a
+ dec de
+ dec b
+ jr nz, .loop
+
+; If bit 6 or 7 of the menu flags is set, set bit 0 of the address
+; at 7:[wWindowStackPointer], and draw the menu using the coordinates from the header.
+; Otherwise, reset bit 0 of 7:[wWindowStackPointer].
+ ld a, [wMenuFlags]
+ bit 6, a
+ jr nz, .bit_6
+ bit 7, a
+ jr z, .not_bit_7
+
+.bit_6
+ ld hl, wWindowStackPointer
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ set 0, [hl]
+ call MenuBoxCoord2Tile
+ call GetMenuBoxDims
+ inc b
+ inc c
+ call .ret ; empty function
+
+.row
+ push bc
+ push hl
+
+.col
+ ld a, [hli]
+ ld [de], a
+ dec de
+ dec c
+ jr nz, .col
+
+ pop hl
+ ld bc, SCREEN_WIDTH
+ add hl, bc
+ pop bc
+ dec b
+ jr nz, .row
+ jr .done
+
+.not_bit_7
+ pop hl ; last-pushed register was de
+ push hl
+ ld a, [hld]
+ ld l, [hl]
+ ld h, a
+ res 0, [hl]
+
+.done
+ pop hl
+ call .ret ; empty function
+ ld a, h
+ ld [de], a
+ dec de
+ ld a, l
+ ld [de], a
+ dec de
+ ld hl, wWindowStackPointer
+ ld [hl], e
+ inc hl
+ ld [hl], d
+
+ call CloseSRAM
+ ld hl, wWindowStackSize
+ inc [hl]
+ ret
+
+.ret
+ ret
+
+_ExitMenu::
+ xor a
+ ldh [hBGMapMode], a
+
+ xor a ; BANK(sWindowStack)
+ call OpenSRAM
+
+ call GetWindowStackTop
+ ld a, l
+ or h
+ jp z, Error_Cant_ExitMenu
+ ld a, l
+ ld [wWindowStackPointer], a
+ ld a, h
+ ld [wWindowStackPointer + 1], a
+ call PopWindow
+ ld a, [wMenuFlags]
+ bit 0, a
+ jr z, .loop
+ ld d, h
+ ld e, l
+ call RestoreTileBackup
+
+.loop
+ call GetWindowStackTop
+ ld a, h
+ or l
+ jr z, .done
+ call PopWindow
+
+.done
+ call CloseSRAM
+ ld hl, wWindowStackSize
+ dec [hl]
+ call Function2434b
+ ld a, [wSpriteUpdatesEnabled]
+ cp 0
+ ret z
+ call ReloadPalettes
+ ret
+
+Function2434b:
+ ld a, [wVramState]
+ bit 0, a
+ ret z
+ xor a ; sScratch
+ call OpenSRAM
+ hlcoord 0, 0
+ ld de, sScratch
+ ld bc, SCREEN_WIDTH * SCREEN_HEIGHT
+ call CopyBytes
+ call CloseSRAM
+ call OverworldTextModeSwitch
+ xor a ; sScratch
+ call OpenSRAM
+ ld hl, sScratch
+ decoord 0, 0
+ ld bc, SCREEN_WIDTH * SCREEN_HEIGHT
+.loop
+ ld a, [hl]
+ cp $61
+ jr c, .next
+ ld [de], a
+.next
+ inc hl
+ inc de
+ dec bc
+ ld a, c
+ or b
+ jr nz, .loop
+ call CloseSRAM
+ ret
+
+Error_Cant_ExitMenu:
+ ld hl, .WindowPoppingErrorText
+ call PrintText
+ call WaitBGMap
+.infinite_loop
+ jr .infinite_loop
+
+.WindowPoppingErrorText:
+ text_far _WindowPoppingErrorText
+ text_end
+
+_InitVerticalMenuCursor::
+ ld a, [wMenuDataFlags]
+ ld b, a
+ ld hl, w2DMenuCursorInitY
+ ld a, [wMenuBorderTopCoord]
+ inc a
+ bit 6, b
+ jr nz, .skip_offset
+ inc a
+.skip_offset
+ ld [hli], a
+; w2DMenuCursorInitX
+ ld a, [wMenuBorderLeftCoord]
+ inc a
+ ld [hli], a
+; w2DMenuNumRows
+ ld a, [wMenuDataItems]
+ ld [hli], a
+; w2DMenuNumCols
+ ld a, 1
+ ld [hli], a
+; w2DMenuFlags1
+ ld [hl], $0
+ bit 5, b
+ jr z, .skip_bit_5
+ set 5, [hl]
+.skip_bit_5
+ ld a, [wMenuFlags]
+ bit 4, a
+ jr z, .skip_bit_6
+ set 6, [hl]
+.skip_bit_6
+ inc hl
+; w2DMenuFlags2
+ xor a
+ ld [hli], a
+; w2DMenuCursorOffsets
+ ln a, 2, 0
+ ld [hli], a
+; wMenuJoypadFilter
+ ld a, A_BUTTON
+ bit 0, b
+ jr nz, .skip_bit_1
+ add B_BUTTON
+.skip_bit_1
+ ld [hli], a
+; wMenuCursorY
+ ld a, [wMenuCursorBuffer]
+ and a
+ jr z, .load_at_the_top
+ ld c, a
+ ld a, [wMenuDataItems]
+ cp c
+ jr nc, .load_position
+.load_at_the_top
+ ld c, 1
+.load_position
+ ld [hl], c
+ inc hl
+; wMenuCursorX
+ ld a, 1
+ ld [hli], a
+; wCursorOffCharacter, wCursorCurrentTile
+ xor a
+ ld [hli], a
+ ld [hli], a
+ ld [hli], a
+ ret
diff --git a/engine/menus/menu_2.asm b/engine/menus/menu_2.asm
new file mode 100644
index 00000000..2fd65e2b
--- /dev/null
+++ b/engine/menus/menu_2.asm
@@ -0,0 +1,298 @@
+PlaceMenuItemName:
+ push de
+ ld a, [wMenuSelection]
+ ld [wNamedObjectIndexBuffer], a
+ call GetItemName
+ pop hl
+ call PlaceString
+ ret
+
+PlaceMenuItemQuantity:
+ push de
+ ld a, [wMenuSelection]
+ ld [wCurItem], a
+ farcall _CheckTossableItem
+ ld a, [wItemAttributeParamBuffer]
+ pop hl
+ and a
+ jr nz, .done
+ ld de, $15
+ add hl, de
+ ld [hl], "×"
+ inc hl
+ ld de, wMenuSelectionQuantity
+ lb bc, 1, 2
+ call PrintNum
+
+.done
+ ret
+
+PlaceMoneyTopRight:
+ ld hl, MenuHeader_0x24a3d
+ call CopyMenuHeader
+ jr PlaceMoneyTextbox
+
+PlaceMoneyBottomLeft:
+ ld hl, MenuHeader_0x24a45
+ call CopyMenuHeader
+ jr PlaceMoneyTextbox
+
+PlaceMoneyAtTopLeftOfTextbox:
+ ld hl, MenuHeader_0x24a3d
+ lb de, 0, 11
+ call OffsetMenuHeader
+
+PlaceMoneyTextbox:
+ call MenuBox
+ call MenuBoxCoord2Tile
+ ld de, SCREEN_WIDTH + 1
+ add hl, de
+ ld de, wMoney
+ lb bc, PRINTNUM_MONEY | 3, 6
+ call PrintNum
+ ret
+
+MenuHeader_0x24a3d:
+ db MENU_BACKUP_TILES ; flags
+ menu_coords 11, 0, SCREEN_WIDTH - 1, 2
+ dw NULL
+ db 1 ; default option
+
+MenuHeader_0x24a45:
+ db MENU_BACKUP_TILES ; flags
+ menu_coords 0, 11, 8, 13
+ dw NULL
+ db 1 ; default option
+
+DisplayCoinCaseBalance:
+ ; Place a text box of size 1x7 at 11, 0.
+ hlcoord 11, 0
+ ld b, 1
+ ld c, 7
+ call Textbox
+ hlcoord 12, 0
+ ld de, CoinString
+ call PlaceString
+ hlcoord 17, 1
+ ld de, ShowMoney_TerminatorString
+ call PlaceString
+ ld de, wCoins
+ lb bc, 2, 4
+ hlcoord 13, 1
+ call PrintNum
+ ret
+
+DisplayMoneyAndCoinBalance:
+ hlcoord 5, 0
+ ld b, 3
+ ld c, 13
+ call Textbox
+ hlcoord 6, 1
+ ld de, MoneyString
+ call PlaceString
+ hlcoord 12, 1
+ ld de, wMoney
+ lb bc, PRINTNUM_MONEY | 3, 6
+ call PrintNum
+ hlcoord 6, 3
+ ld de, CoinString
+ call PlaceString
+ hlcoord 15, 3
+ ld de, wCoins
+ lb bc, 2, 4
+ call PrintNum
+ ret
+
+MoneyString:
+ db "MONEY@"
+CoinString:
+ db "COIN@"
+ShowMoney_TerminatorString:
+ db "@@"
+
+Unreferenced_Function24ab8:
+; related to safari?
+ ld hl, wOptions
+ ld a, [hl]
+ push af
+ set NO_TEXT_SCROLL, [hl]
+ hlcoord 0, 0
+ ld b, 3
+ ld c, 7
+ call Textbox
+ hlcoord 1, 1
+ ld de, wSafariTimeRemaining
+ lb bc, 2, 3
+ call PrintNum
+ hlcoord 4, 1
+ ld de, .slash_500
+ call PlaceString
+ hlcoord 1, 3
+ ld de, .booru_ko
+ call PlaceString
+ hlcoord 5, 3
+ ld de, wSafariBallsRemaining
+ lb bc, 1, 2
+ call PrintNum
+ pop af
+ ld [wOptions], a
+ ret
+
+.slash_500
+ db "/500@"
+.booru_ko
+ db "ボール   こ@"
+
+StartMenu_DrawBugContestStatusBox:
+ hlcoord 0, 0
+ ld b, 5
+ ld c, 17
+ call Textbox
+ ret
+
+StartMenu_PrintBugContestStatus:
+ ld hl, wOptions
+ ld a, [hl]
+ push af
+ set NO_TEXT_SCROLL, [hl]
+ call StartMenu_DrawBugContestStatusBox
+ hlcoord 1, 5
+ ld de, .Balls_EN
+ call PlaceString
+ hlcoord 8, 5
+ ld de, wParkBallsRemaining
+ lb bc, PRINTNUM_LEFTALIGN | 1, 2
+ call PrintNum
+ hlcoord 1, 1
+ ld de, .CAUGHT
+ call PlaceString
+ ld a, [wContestMon]
+ and a
+ ld de, .None
+ jr z, .no_contest_mon
+ ld [wNamedObjectIndexBuffer], a
+ call GetPokemonName
+
+.no_contest_mon
+ hlcoord 8, 1
+ call PlaceString
+ ld a, [wContestMon]
+ and a
+ jr z, .skip_level
+ hlcoord 1, 3
+ ld de, .LEVEL
+ call PlaceString
+ ld a, [wContestMonLevel]
+ ld h, b
+ ld l, c
+ inc hl
+ ld c, 3
+ call Print8BitNumLeftAlign
+
+.skip_level
+ pop af
+ ld [wOptions], a
+ ret
+
+.Balls_JP:
+ db "ボール   こ@"
+.CAUGHT:
+ db "CAUGHT@"
+.Balls_EN:
+ db "BALLS:@"
+.None:
+ db "None@"
+.LEVEL:
+ db "LEVEL@"
+
+Kurt_SelectApricorn:
+ call FindApricornsInBag
+ jr c, .nope
+ ld hl, .MenuHeader
+ call LoadMenuHeader
+ call DoNthMenu
+ call CloseWindow
+ jr c, .nope
+ ld a, [wMenuSelection]
+ jr .done
+
+.nope
+ xor a ; FALSE
+
+.done
+ ld c, a
+ ret
+
+.MenuHeader:
+ db MENU_BACKUP_TILES ; flags
+ menu_coords 0, 0, 14, 17
+ dw .MenuData
+ db 1 ; default option
+
+.MenuData:
+ db SCROLLINGMENU_ENABLE_SELECT | SCROLLINGMENU_ENABLE_FUNCTION3
+ dbw 0, wBuffer1
+ dw .Name
+ dw NULL
+
+.Name:
+ ld a, [wMenuSelection]
+ and a
+ jp nz, PlaceMenuItemName
+ ld h, d
+ ld l, e
+ ld de, .Cancel
+ call PlaceString
+ ret
+
+.Cancel
+ db "CANCEL@"
+
+FindApricornsInBag:
+; Checks the bag for Apricorns.
+ ld hl, wBuffer1
+ xor a
+ ld [hli], a
+ dec a
+ ld bc, 10
+ call ByteFill
+
+ ld hl, ApricornBalls
+.loop
+ ld a, [hl]
+ cp -1
+ jr z, .done
+ push hl
+ ld [wCurItem], a
+ ld hl, wNumItems
+ call CheckItem
+ pop hl
+ jr nc, .nope
+ ld a, [hl]
+ call .addtobuffer
+.nope
+ inc hl
+ inc hl
+ jr .loop
+
+.done
+ xor a
+ call .addtobuffer
+ ld a, [wBuffer1]
+ cp 1
+ ret nz
+ scf
+ ret
+
+.addtobuffer
+ push hl
+ ld hl, wBuffer1
+ inc [hl]
+ ld e, [hl]
+ ld d, 0
+ add hl, de
+ ld [hl], a
+ pop hl
+ ret
+
+INCLUDE "data/items/apricorn_balls.asm"
diff --git a/engine/menus/naming_screen.asm b/engine/menus/naming_screen.asm
new file mode 100644
index 00000000..34e2c6f2
--- /dev/null
+++ b/engine/menus/naming_screen.asm
@@ -0,0 +1,1377 @@
+NAMINGSCREEN_CURSOR EQU $7e
+
+NAMINGSCREEN_BORDER EQU "■" ; $60
+NAMINGSCREEN_MIDDLELINE EQU "→" ; $eb
+NAMINGSCREEN_UNDERLINE EQU "<DOT>" ; $f2
+
+_NamingScreen:
+ call DisableSpriteUpdates
+ call NamingScreen
+ call ReturnToMapWithSpeechTextbox
+ ret
+
+NamingScreen:
+ ld hl, wNamingScreenDestinationPointer
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ld hl, wNamingScreenType
+ ld [hl], b
+ ld hl, wOptions
+ ld a, [hl]
+ push af
+ set NO_TEXT_SCROLL, [hl]
+ ldh a, [hMapAnims]
+ push af
+ xor a
+ ldh [hMapAnims], a
+ ldh a, [hInMenu]
+ push af
+ ld a, $1
+ ldh [hInMenu], a
+ call .SetUpNamingScreen
+ call DelayFrame
+.loop
+ call NamingScreenJoypadLoop
+ jr nc, .loop
+ pop af
+ ldh [hInMenu], a
+ pop af
+ ldh [hMapAnims], a
+ pop af
+ ld [wOptions], a
+ call ClearJoypad
+ ret
+
+.SetUpNamingScreen:
+ call ClearBGPalettes
+ ld b, SCGB_DIPLOMA
+ call GetSGBLayout
+ call DisableLCD
+ call LoadNamingScreenGFX
+ call NamingScreen_InitText
+ ld a, LCDC_DEFAULT
+ ldh [rLCDC], a
+ call .GetNamingScreenSetup
+ call WaitBGMap
+ call WaitTop
+ call SetPalettes
+ call NamingScreen_InitNameEntry
+ ret
+
+.GetNamingScreenSetup:
+ ld a, [wNamingScreenType]
+ maskbits NUM_NAME_TYPES
+ ld e, a
+ ld d, 0
+ ld hl, .Jumptable
+ add hl, de
+ add hl, de
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ jp hl
+
+.Jumptable:
+; entries correspond to NAME_* constants
+ dw .Pokemon
+ dw .Player
+ dw .Rival
+ dw .Mom
+ dw .Box
+ dw .Pokemon
+ dw .Pokemon
+ dw .Pokemon
+
+.Pokemon:
+ ld a, [wCurPartySpecies]
+ ld [wTempIconSpecies], a
+ ld hl, LoadMenuMonIcon
+ ld a, BANK(LoadMenuMonIcon)
+ ld e, MONICON_NAMINGSCREEN
+ rst FarCall
+ ld a, [wCurPartySpecies]
+ ld [wNamedObjectIndexBuffer], a
+ call GetPokemonName
+ hlcoord 5, 2
+ call PlaceString
+ ld l, c
+ ld h, b
+ ld de, .NicknameStrings
+ call PlaceString
+ inc de
+ hlcoord 5, 4
+ call PlaceString
+ farcall GetGender
+ jr c, .genderless
+ ld a, "♂"
+ jr nz, .place_gender
+ ld a, "♀"
+.place_gender
+ hlcoord 1, 2
+ ld [hl], a
+.genderless
+ call .StoreMonIconParams
+ ret
+
+.NicknameStrings:
+ db "'S@"
+ db "NICKNAME?@"
+
+.Player:
+ ld de, ChrisSpriteGFX
+ call .LoadSprite
+ hlcoord 5, 2
+ ld de, .PlayerNameString
+ call PlaceString
+ call .StoreSpriteIconParams
+ ret
+
+.PlayerNameString:
+ db "YOUR NAME?@"
+
+.Rival:
+ ld de, SilverSpriteGFX
+ call .LoadSprite
+ hlcoord 5, 2
+ ld de, .RivalNameString
+ call PlaceString
+ call .StoreSpriteIconParams
+ ret
+
+.RivalNameString:
+ db "RIVAL'S NAME?@"
+
+.Mom:
+ ld de, MomSpriteGFX
+ call .LoadSprite
+ hlcoord 5, 2
+ ld de, .MomNameString
+ call PlaceString
+ call .StoreSpriteIconParams
+ ret
+
+.MomNameString:
+ db "MOTHER'S NAME?@"
+
+.Box:
+ ld de, PokeBallSpriteGFX
+ ld hl, vTiles0 tile $00
+ lb bc, BANK(PokeBallSpriteGFX), 4
+ call Request2bpp
+ xor a
+ ld hl, wSpriteAnimDict
+ ld [hli], a
+ ld [hl], a
+ depixel 4, 4, 4, 0
+ ld a, SPRITE_ANIM_INDEX_RED_WALK
+ call InitSpriteAnimStruct
+ ld hl, SPRITEANIMSTRUCT_FRAMESET_ID
+ add hl, bc
+ ld [hl], $0
+ hlcoord 5, 2
+ ld de, .BoxNameString
+ call PlaceString
+ call .StoreBoxIconParams
+ ret
+
+.BoxNameString:
+ db "BOX NAME?@"
+
+.LoadSprite:
+ push de
+ ld hl, vTiles0 tile $00
+ lb bc, BANK(ChrisSpriteGFX), 4
+ call Request2bpp
+ pop de
+ ld hl, 12 tiles
+ add hl, de
+ ld e, l
+ ld d, h
+ ld hl, vTiles0 tile $04
+ lb bc, BANK(ChrisSpriteGFX), 4
+ call Request2bpp
+ xor a
+ ld hl, wSpriteAnimDict
+ ld [hli], a
+ ld [hl], a
+ lb de, $24, $20
+ ld a, SPRITE_ANIM_INDEX_RED_WALK
+ call InitSpriteAnimStruct
+ ret
+
+.StoreMonIconParams:
+ ld a, MON_NAME_LENGTH - 1
+ hlcoord 5, 6
+ jr .StoreParams
+
+.StoreSpriteIconParams:
+ ld a, PLAYER_NAME_LENGTH - 1
+ hlcoord 5, 6
+ jr .StoreParams
+
+.StoreBoxIconParams:
+ ld a, BOX_NAME_LENGTH - 1
+ hlcoord 5, 4
+ jr .StoreParams
+
+.StoreParams:
+ ld [wNamingScreenMaxNameLength], a
+ ld a, l
+ ld [wNamingScreenStringEntryCoord], a
+ ld a, h
+ ld [wNamingScreenStringEntryCoord + 1], a
+ ret
+
+NamingScreen_IsTargetBox:
+ push bc
+ push af
+ ld a, [wNamingScreenType]
+ sub $3
+ ld b, a
+ pop af
+ dec b
+ pop bc
+ ret
+
+NamingScreen_InitText:
+ call WaitTop
+ hlcoord 0, 0
+ ld bc, SCREEN_WIDTH * SCREEN_HEIGHT
+ ld a, NAMINGSCREEN_BORDER
+ call ByteFill
+ hlcoord 1, 1
+ lb bc, 6, 18
+ call NamingScreen_IsTargetBox
+ jr nz, .not_box
+ lb bc, 4, 18
+
+.not_box
+ call ClearBox
+ ld de, NameInputUpper
+NamingScreen_ApplyTextInputMode:
+ call NamingScreen_IsTargetBox
+ jr nz, .not_box
+ ld hl, BoxNameInputLower - NameInputLower
+ add hl, de
+ ld d, h
+ ld e, l
+
+.not_box
+ push de
+ hlcoord 1, 8
+ lb bc, 7, 18
+ call NamingScreen_IsTargetBox
+ jr nz, .not_box_2
+ hlcoord 1, 6
+ lb bc, 9, 18
+
+.not_box_2
+ call ClearBox
+ hlcoord 1, 16
+ lb bc, 1, 18
+ call ClearBox
+ pop de
+ hlcoord 2, 8
+ ld b, $5
+ call NamingScreen_IsTargetBox
+ jr nz, .row
+ hlcoord 2, 6
+ ld b, $6
+
+.row
+ ld c, $11
+.col
+ ld a, [de]
+ ld [hli], a
+ inc de
+ dec c
+ jr nz, .col
+ push de
+ ld de, 2 * SCREEN_WIDTH - $11
+ add hl, de
+ pop de
+ dec b
+ jr nz, .row
+ ret
+
+NamingScreenJoypadLoop:
+ call JoyTextDelay
+ ld a, [wJumptableIndex]
+ bit 7, a
+ jr nz, .quit
+ call .RunJumptable
+ farcall PlaySpriteAnimationsAndDelayFrame
+ call .UpdateStringEntry
+ call DelayFrame
+ and a
+ ret
+
+.quit
+ callfar ClearSpriteAnims
+ call ClearSprites
+ xor a
+ ldh [hSCX], a
+ ldh [hSCY], a
+ scf
+ ret
+
+.UpdateStringEntry:
+ xor a
+ ldh [hBGMapMode], a
+ hlcoord 1, 5
+ call NamingScreen_IsTargetBox
+ jr nz, .got_coords
+ hlcoord 1, 3
+
+.got_coords
+ lb bc, 1, 18
+ call ClearBox
+ ld hl, wNamingScreenDestinationPointer
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ld hl, wNamingScreenStringEntryCoord
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ call PlaceString
+ ld a, $1
+ ldh [hBGMapMode], a
+ ret
+
+.RunJumptable:
+ ld a, [wJumptableIndex]
+ ld e, a
+ ld d, $0
+ ld hl, .Jumptable
+ add hl, de
+ add hl, de
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ jp hl
+
+.Jumptable:
+ dw .InitCursor
+ dw .ReadButtons
+
+.InitCursor:
+ depixel 10, 3
+ call NamingScreen_IsTargetBox
+ jr nz, .got_cursor_position
+ ld d, 8 * 8
+.got_cursor_position
+ ld a, SPRITE_ANIM_INDEX_NAMING_SCREEN_CURSOR
+ call InitSpriteAnimStruct
+ ld a, c
+ ld [wNamingScreenCursorObjectPointer], a
+ ld a, b
+ ld [wNamingScreenCursorObjectPointer + 1], a
+ ld hl, SPRITEANIMSTRUCT_FRAMESET_ID
+ add hl, bc
+ ld a, [hl]
+ ld hl, SPRITEANIMSTRUCT_0E
+ add hl, bc
+ ld [hl], a
+ ld hl, wJumptableIndex
+ inc [hl]
+ ret
+
+.ReadButtons:
+ ld hl, hJoyPressed
+ ld a, [hl]
+ and A_BUTTON
+ jr nz, .a
+ ld a, [hl]
+ and B_BUTTON
+ jr nz, .b
+ ld a, [hl]
+ and START
+ jr nz, .start
+ ld a, [hl]
+ and SELECT
+ jr nz, .select
+ ret
+
+.a
+ call .GetCursorPosition
+ cp $1
+ jr z, .select
+ cp $2
+ jr z, .b
+ cp $3
+ jr z, .end
+ call NamingScreen_GetLastCharacter
+ call NamingScreen_TryAddCharacter
+ ret nc
+
+.start
+ ld hl, wNamingScreenCursorObjectPointer
+ ld c, [hl]
+ inc hl
+ ld b, [hl]
+ ld hl, SPRITEANIMSTRUCT_0C
+ add hl, bc
+ ld [hl], $8
+ ld hl, SPRITEANIMSTRUCT_0D
+ add hl, bc
+ ld [hl], $4
+ call NamingScreen_IsTargetBox
+ ret nz
+ inc [hl]
+ ret
+
+.b
+ call NamingScreen_DeleteCharacter
+ ret
+
+.end
+ call NamingScreen_StoreEntry
+ ld hl, wJumptableIndex
+ set 7, [hl]
+ ret
+
+.select
+ ld hl, wNamingScreenLetterCase
+ ld a, [hl]
+ xor 1
+ ld [hl], a
+ jr z, .upper
+ ld de, NameInputLower
+ call NamingScreen_ApplyTextInputMode
+ ret
+
+.upper
+ ld de, NameInputUpper
+ call NamingScreen_ApplyTextInputMode
+ ret
+
+.GetCursorPosition:
+ ld hl, wNamingScreenCursorObjectPointer
+ ld c, [hl]
+ inc hl
+ ld b, [hl]
+
+NamingScreen_GetCursorPosition:
+ ld hl, SPRITEANIMSTRUCT_0D
+ add hl, bc
+ ld a, [hl]
+ push bc
+ ld b, $4
+ call NamingScreen_IsTargetBox
+ jr nz, .not_box
+ inc b
+.not_box
+ cp b
+ pop bc
+ jr nz, .not_bottom_row
+ ld hl, SPRITEANIMSTRUCT_0C
+ add hl, bc
+ ld a, [hl]
+ cp $3
+ jr c, .case_switch
+ cp $6
+ jr c, .delete
+ ld a, $3
+ ret
+
+.case_switch
+ ld a, $1
+ ret
+
+.delete
+ ld a, $2
+ ret
+
+.not_bottom_row
+ xor a
+ ret
+
+NamingScreen_AnimateCursor:
+ call .GetDPad
+ ld hl, SPRITEANIMSTRUCT_0D
+ add hl, bc
+ ld a, [hl]
+ ld e, a
+ swap e
+ ld hl, SPRITEANIMSTRUCT_YOFFSET
+ add hl, bc
+ ld [hl], e
+ ld d, $4
+ call NamingScreen_IsTargetBox
+ jr nz, .ok
+ inc d
+.ok
+ cp d
+ ld de, .LetterEntries
+ ld a, SPRITE_ANIM_FRAMESET_TEXT_ENTRY_CURSOR - SPRITE_ANIM_FRAMESET_TEXT_ENTRY_CURSOR ; 0
+ jr nz, .ok2
+ ld de, .CaseDelEnd
+ ld a, SPRITE_ANIM_FRAMESET_TEXT_ENTRY_CURSOR_BIG - SPRITE_ANIM_FRAMESET_TEXT_ENTRY_CURSOR ; 1
+.ok2
+ ld hl, SPRITEANIMSTRUCT_0E
+ add hl, bc
+ add [hl] ; default SPRITE_ANIM_FRAMESET_TEXT_ENTRY_CURSOR
+ ld hl, SPRITEANIMSTRUCT_FRAMESET_ID
+ add hl, bc
+ ld [hl], a
+ ld hl, SPRITEANIMSTRUCT_0C
+ add hl, bc
+ ld l, [hl]
+ ld h, $0
+ add hl, de
+ ld a, [hl]
+ ld hl, SPRITEANIMSTRUCT_XOFFSET
+ add hl, bc
+ ld [hl], a
+ ret
+
+.LetterEntries:
+ db $00, $10, $20, $30, $40, $50, $60, $70, $80
+
+.CaseDelEnd:
+ db $00, $00, $00, $30, $30, $30, $60, $60, $60
+
+.GetDPad:
+ ld hl, hJoyLast
+ ld a, [hl]
+ and D_UP
+ jr nz, .up
+ ld a, [hl]
+ and D_DOWN
+ jr nz, .down
+ ld a, [hl]
+ and D_LEFT
+ jr nz, .left
+ ld a, [hl]
+ and D_RIGHT
+ jr nz, .right
+ ret
+
+.right
+ call NamingScreen_GetCursorPosition
+ and a
+ jr nz, .asm_11e76
+ ld hl, SPRITEANIMSTRUCT_0C
+ add hl, bc
+ ld a, [hl]
+ cp $8
+ jr nc, .asm_11e73
+ inc [hl]
+ ret
+
+.asm_11e73
+ ld [hl], $0
+ ret
+
+.asm_11e76
+ cp $3
+ jr nz, .asm_11e7b
+ xor a
+.asm_11e7b
+ ld e, a
+ add a
+ add e
+ ld hl, SPRITEANIMSTRUCT_0C
+ add hl, bc
+ ld [hl], a
+ ret
+
+.left
+ call NamingScreen_GetCursorPosition
+ and a
+ jr nz, .asm_11e97
+ ld hl, SPRITEANIMSTRUCT_0C
+ add hl, bc
+ ld a, [hl]
+ and a
+ jr z, .asm_11e94
+ dec [hl]
+ ret
+
+.asm_11e94
+ ld [hl], $8
+ ret
+
+.asm_11e97
+ cp $1
+ jr nz, .asm_11e9d
+ ld a, $4
+.asm_11e9d
+ dec a
+ dec a
+ ld e, a
+ add a
+ add e
+ ld hl, SPRITEANIMSTRUCT_0C
+ add hl, bc
+ ld [hl], a
+ ret
+
+.down
+ ld hl, SPRITEANIMSTRUCT_0D
+ add hl, bc
+ ld a, [hl]
+ call NamingScreen_IsTargetBox
+ jr nz, .asm_11eb8
+ cp $5
+ jr nc, .asm_11ebe
+ inc [hl]
+ ret
+
+.asm_11eb8
+ cp $4
+ jr nc, .asm_11ebe
+ inc [hl]
+ ret
+
+.asm_11ebe
+ ld [hl], $0
+ ret
+
+.up
+ ld hl, SPRITEANIMSTRUCT_0D
+ add hl, bc
+ ld a, [hl]
+ and a
+ jr z, .asm_11ecb
+ dec [hl]
+ ret
+
+.asm_11ecb
+ ld [hl], $4
+ call NamingScreen_IsTargetBox
+ ret nz
+ inc [hl]
+ ret
+
+NamingScreen_TryAddCharacter:
+ ld a, [wNamingScreenLastCharacter]
+ ld hl, Dakutens
+ cp "゙" ; $e5
+ jr z, asm_11f06
+ ld hl, Handakutens
+ cp "゚" ; $e4
+ jr z, asm_11f06
+
+MailComposition_TryAddCharacter:
+ ld a, [wNamingScreenMaxNameLength]
+ ld c, a
+ ld a, [wNamingScreenCurNameLength]
+ cp c
+ ret nc
+
+ ld a, [wNamingScreenLastCharacter]
+
+NamingScreen_LoadNextCharacter:
+ call NamingScreen_GetTextCursorPosition
+ ld [hl], a
+
+NamingScreen_AdvanceCursor_CheckEndOfString:
+ ld hl, wNamingScreenCurNameLength
+ inc [hl]
+ call NamingScreen_GetTextCursorPosition
+ ld a, [hl]
+ cp "@"
+ jr z, .end_of_string
+ ld [hl], NAMINGSCREEN_UNDERLINE
+ and a
+ ret
+
+.end_of_string
+ scf
+ ret
+
+asm_11f06:
+ ld a, [wNamingScreenCurNameLength]
+ and a
+ ret z
+ push hl
+ ld hl, wNamingScreenCurNameLength
+ dec [hl]
+ call NamingScreen_GetTextCursorPosition
+ ld c, [hl]
+ pop hl
+
+.loop
+ ld a, [hli]
+ cp $ff
+ jr z, NamingScreen_AdvanceCursor_CheckEndOfString
+ cp c
+ jr z, .done
+ inc hl
+ jr .loop
+
+.done
+ ld a, [hl]
+ jr NamingScreen_LoadNextCharacter
+
+INCLUDE "data/text/dakutens.asm"
+
+NamingScreen_DeleteCharacter:
+ ld hl, wNamingScreenCurNameLength
+ ld a, [hl]
+ and a
+ ret z
+ dec [hl]
+ call NamingScreen_GetTextCursorPosition
+ ld [hl], NAMINGSCREEN_UNDERLINE
+ inc hl
+ ld a, [hl]
+ cp NAMINGSCREEN_UNDERLINE
+ ret nz
+ ld [hl], NAMINGSCREEN_MIDDLELINE
+ ret
+
+NamingScreen_GetTextCursorPosition:
+ push af
+ ld hl, wNamingScreenDestinationPointer
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld a, [wNamingScreenCurNameLength]
+ ld e, a
+ ld d, 0
+ add hl, de
+ pop af
+ ret
+
+NamingScreen_InitNameEntry:
+; load NAMINGSCREEN_UNDERLINE, (NAMINGSCREEN_MIDDLELINE * [wNamingScreenMaxNameLength]), "@" into the dw address at wNamingScreenDestinationPointer
+ ld hl, wNamingScreenDestinationPointer
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld [hl], NAMINGSCREEN_UNDERLINE
+ inc hl
+ ld a, [wNamingScreenMaxNameLength]
+ dec a
+ ld c, a
+ ld a, NAMINGSCREEN_MIDDLELINE
+.loop
+ ld [hli], a
+ dec c
+ jr nz, .loop
+ ld [hl], "@"
+ ret
+
+NamingScreen_StoreEntry:
+ ld hl, wNamingScreenDestinationPointer
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld a, [wNamingScreenMaxNameLength]
+ ld c, a
+.loop
+ ld a, [hl]
+ cp NAMINGSCREEN_MIDDLELINE
+ jr z, .terminator
+ cp NAMINGSCREEN_UNDERLINE
+ jr nz, .not_terminator
+.terminator
+ ld [hl], "@"
+.not_terminator
+ inc hl
+ dec c
+ jr nz, .loop
+ ret
+
+NamingScreen_GetLastCharacter:
+ ld hl, wNamingScreenCursorObjectPointer
+ ld c, [hl]
+ inc hl
+ ld b, [hl]
+ ld hl, SPRITEANIMSTRUCT_XOFFSET
+ add hl, bc
+ ld a, [hl]
+ ld hl, SPRITEANIMSTRUCT_XCOORD
+ add hl, bc
+ add [hl]
+ sub $8
+ srl a
+ srl a
+ srl a
+ ld e, a
+ ld hl, SPRITEANIMSTRUCT_YOFFSET
+ add hl, bc
+ ld a, [hl]
+ ld hl, SPRITEANIMSTRUCT_YCOORD
+ add hl, bc
+ add [hl]
+ sub $10
+ srl a
+ srl a
+ srl a
+ ld d, a
+ hlcoord 0, 0
+ ld bc, SCREEN_WIDTH
+.loop
+ ld a, d
+ and a
+ jr z, .done
+ add hl, bc
+ dec d
+ jr .loop
+
+.done
+ add hl, de
+ ld a, [hl]
+ ld [wNamingScreenLastCharacter], a
+ ret
+
+LoadNamingScreenGFX:
+ call ClearSprites
+ callfar ClearSpriteAnims
+ call LoadStandardFont
+ call LoadFontsExtra
+
+ ld de, NamingScreenGFX_MiddleLine
+ ld hl, vTiles0 tile NAMINGSCREEN_MIDDLELINE
+ lb bc, BANK(NamingScreenGFX_MiddleLine), 1
+ call Get1bpp
+
+ ld de, NamingScreenGFX_UnderLine
+ ld hl, vTiles0 tile NAMINGSCREEN_UNDERLINE
+ lb bc, BANK(NamingScreenGFX_UnderLine), 1
+ call Get1bpp
+
+ ld de, vTiles2 tile NAMINGSCREEN_BORDER
+ ld hl, NamingScreenGFX_Border
+ ld bc, 1 tiles
+ ld a, BANK(NamingScreenGFX_Border)
+ call FarCopyBytes
+
+ ld de, vTiles0 tile NAMINGSCREEN_CURSOR
+ ld hl, NamingScreenGFX_Cursor
+ ld bc, 2 tiles
+ ld a, BANK(NamingScreenGFX_Cursor)
+ call FarCopyBytes
+
+ ld a, $5
+ ld hl, wSpriteAnimDict + 9 * 2
+ ld [hli], a
+ ld [hl], NAMINGSCREEN_CURSOR
+ xor a
+ ldh [hSCY], a
+ ld [wGlobalAnimYOffset], a
+ ldh [hSCX], a
+ ld [wGlobalAnimXOffset], a
+ ld [wJumptableIndex], a
+ ld [wNamingScreenLetterCase], a
+ ldh [hBGMapMode], a
+ ld [wNamingScreenCurNameLength], a
+ ld a, $7
+ ldh [hWX], a
+ ret
+
+NamingScreenGFX_Border:
+INCBIN "gfx/naming_screen/border.2bpp"
+
+NamingScreenGFX_Cursor:
+INCBIN "gfx/naming_screen/cursor.2bpp"
+
+INCLUDE "data/text/name_input_chars.asm"
+
+NamingScreenGFX_End: ; unused
+INCBIN "gfx/naming_screen/end.1bpp"
+
+NamingScreenGFX_MiddleLine:
+INCBIN "gfx/naming_screen/middle_line.1bpp"
+
+NamingScreenGFX_UnderLine:
+INCBIN "gfx/naming_screen/underline.1bpp"
+
+_ComposeMailMessage:
+ ld hl, wNamingScreenDestinationPointer
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ldh a, [hMapAnims]
+ push af
+ xor a
+ ldh [hMapAnims], a
+ ldh a, [hInMenu]
+ push af
+ ld a, $1
+ ldh [hInMenu], a
+ call .InitBlankMail
+ call DelayFrame
+
+.loop
+ call .DoMailEntry
+ jr nc, .loop
+
+ pop af
+ ldh [hInMenu], a
+ pop af
+ ldh [hMapAnims], a
+ ret
+
+.InitBlankMail:
+ call ClearBGPalettes
+ call DisableLCD
+ call LoadNamingScreenGFX
+ ld de, vTiles0 tile $00
+ ld hl, .MailIcon
+ ld bc, 8 tiles
+ ld a, BANK(.MailIcon)
+ call FarCopyBytes
+ xor a
+ ld hl, wSpriteAnimDict
+ ld [hli], a
+ ld [hl], a
+
+ ; init mail icon
+ depixel 3, 2
+ ld a, SPRITE_ANIM_INDEX_PARTY_MON
+ call InitSpriteAnimStruct
+
+ ld hl, SPRITEANIMSTRUCT_ANIM_SEQ_ID
+ add hl, bc
+ ld [hl], $0
+ call .InitCharset
+ ld a, LCDC_DEFAULT
+ ldh [rLCDC], a
+ call .initwNamingScreenMaxNameLength
+ ld b, SCGB_DIPLOMA
+ call GetSGBLayout
+ call WaitBGMap
+ call WaitTop
+ ld a, %11100100
+ call DmgToCgbBGPals
+ ld a, %11100100
+ call DmgToCgbObjPal0
+ call NamingScreen_InitNameEntry
+ ld hl, wNamingScreenDestinationPointer
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ld hl, MAIL_LINE_LENGTH
+ add hl, de
+ ld [hl], "<NEXT>"
+ ret
+
+.MailIcon:
+INCBIN "gfx/icons/mail_big.2bpp"
+
+.initwNamingScreenMaxNameLength
+ ld a, MAIL_MSG_LENGTH + 1
+ ld [wNamingScreenMaxNameLength], a
+ ret
+
+.UnusedString11f7a:
+ db "メールを かいてね@"
+
+.InitCharset:
+ call WaitTop
+ hlcoord 0, 0
+ ld bc, 6 * SCREEN_WIDTH
+ ld a, NAMINGSCREEN_BORDER
+ call ByteFill
+ hlcoord 0, 6
+ ld bc, 12 * SCREEN_WIDTH
+ ld a, " "
+ call ByteFill
+ hlcoord 1, 1
+ lb bc, 4, SCREEN_WIDTH - 2
+ call ClearBox
+ ld de, MailEntry_Uppercase
+
+.PlaceMailCharset:
+ hlcoord 1, 7
+ ld b, 6
+.next
+ ld c, SCREEN_WIDTH - 1
+.loop_
+ ld a, [de]
+ ld [hli], a
+ inc de
+ dec c
+ jr nz, .loop_
+ push de
+ ld de, SCREEN_WIDTH + 1
+ add hl, de
+ pop de
+ dec b
+ jr nz, .next
+ ret
+
+.DoMailEntry:
+ call JoyTextDelay
+ ld a, [wJumptableIndex]
+ bit 7, a
+ jr nz, .exit_mail
+ call .DoJumptable
+ farcall PlaySpriteAnimationsAndDelayFrame
+ call .Update
+ call DelayFrame
+ and a
+ ret
+
+.exit_mail
+ callfar ClearSpriteAnims
+ call ClearSprites
+ xor a
+ ldh [hSCX], a
+ ldh [hSCY], a
+ scf
+ ret
+
+.Update:
+ xor a
+ ldh [hBGMapMode], a
+ hlcoord 1, 1
+ lb bc, 4, 18
+ call ClearBox
+ ld hl, wNamingScreenDestinationPointer
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ hlcoord 2, 2
+ call PlaceString
+ ld a, $1
+ ldh [hBGMapMode], a
+ ret
+
+.DoJumptable:
+ ld a, [wJumptableIndex]
+ ld e, a
+ ld d, 0
+ ld hl, .Jumptable
+ add hl, de
+ add hl, de
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ jp hl
+
+.Jumptable:
+ dw .init_blinking_cursor
+ dw .process_joypad
+
+.init_blinking_cursor
+ depixel 9, 2
+ ld a, SPRITE_ANIM_INDEX_COMPOSE_MAIL_CURSOR
+ call InitSpriteAnimStruct
+ ld a, c
+ ld [wNamingScreenCursorObjectPointer], a
+ ld a, b
+ ld [wNamingScreenCursorObjectPointer + 1], a
+ ld hl, SPRITEANIMSTRUCT_FRAMESET_ID
+ add hl, bc
+ ld a, [hl]
+ ld hl, SPRITEANIMSTRUCT_0E
+ add hl, bc
+ ld [hl], a
+ ld hl, wJumptableIndex
+ inc [hl]
+ ret
+
+.process_joypad
+ ld hl, hJoyPressed
+ ld a, [hl]
+ and A_BUTTON
+ jr nz, .a
+ ld a, [hl]
+ and B_BUTTON
+ jr nz, .b
+ ld a, [hl]
+ and START
+ jr nz, .start
+ ld a, [hl]
+ and SELECT
+ jr nz, .select
+ ret
+
+.a
+ call NamingScreen_PressedA_GetCursorCommand
+ cp $1
+ jr z, .select
+ cp $2
+ jr z, .b
+ cp $3
+ jr z, .finished
+ call NamingScreen_GetLastCharacter
+ call MailComposition_TryAddLastCharacter
+ jr c, .start
+ ld hl, wNamingScreenCurNameLength
+ ld a, [hl]
+ cp MAIL_LINE_LENGTH
+ ret nz
+ inc [hl]
+ call NamingScreen_GetTextCursorPosition
+ ld [hl], NAMINGSCREEN_UNDERLINE
+ dec hl
+ ld [hl], "<NEXT>"
+ ret
+
+.start
+ ld hl, wNamingScreenCursorObjectPointer
+ ld c, [hl]
+ inc hl
+ ld b, [hl]
+ ld hl, SPRITEANIMSTRUCT_0C
+ add hl, bc
+ ld [hl], $9
+ ld hl, SPRITEANIMSTRUCT_0D
+ add hl, bc
+ ld [hl], $5
+ ret
+
+.b
+ call NamingScreen_DeleteCharacter
+ ld hl, wNamingScreenCurNameLength
+ ld a, [hl]
+ cp MAIL_LINE_LENGTH
+ ret nz
+ dec [hl]
+ call NamingScreen_GetTextCursorPosition
+ ld [hl], NAMINGSCREEN_UNDERLINE
+ inc hl
+ ld [hl], "<NEXT>"
+ ret
+
+.finished
+ call NamingScreen_StoreEntry
+ ld hl, wJumptableIndex
+ set 7, [hl]
+ ret
+
+.select
+ ld hl, wNamingScreenLetterCase
+ ld a, [hl]
+ xor 1
+ ld [hl], a
+ jr nz, .switch_to_lowercase
+ ld de, MailEntry_Uppercase
+ call .PlaceMailCharset
+ ret
+
+.switch_to_lowercase
+ ld de, MailEntry_Lowercase
+ call .PlaceMailCharset
+ ret
+
+; called from engine/sprite_anims.asm
+
+ComposeMail_AnimateCursor:
+ call .GetDPad
+ ld hl, SPRITEANIMSTRUCT_0D
+ add hl, bc
+ ld a, [hl]
+ ld e, a
+ swap e
+ ld hl, SPRITEANIMSTRUCT_YOFFSET
+ add hl, bc
+ ld [hl], e
+ cp $5
+ ld de, .LetterEntries
+ ld a, 0
+ jr nz, .got_pointer
+ ld de, .CaseDelEnd
+ ld a, 1
+.got_pointer
+ ld hl, SPRITEANIMSTRUCT_0E
+ add hl, bc
+ add [hl]
+ ld hl, SPRITEANIMSTRUCT_FRAMESET_ID
+ add hl, bc
+ ld [hl], a
+ ld hl, SPRITEANIMSTRUCT_0C
+ add hl, bc
+ ld l, [hl]
+ ld h, 0
+ add hl, de
+ ld a, [hl]
+ ld hl, SPRITEANIMSTRUCT_XOFFSET
+ add hl, bc
+ ld [hl], a
+ ret
+
+.LetterEntries:
+ db $00, $10, $20, $30, $40, $50, $60, $70, $80, $90
+
+.CaseDelEnd:
+ db $00, $00, $00, $30, $30, $30, $60, $60, $60, $60
+
+.GetDPad:
+ ld hl, hJoyLast
+ ld a, [hl]
+ and D_UP
+ jr nz, .up
+ ld a, [hl]
+ and D_DOWN
+ jr nz, .down
+ ld a, [hl]
+ and D_LEFT
+ jr nz, .left
+ ld a, [hl]
+ and D_RIGHT
+ jr nz, .right
+ ret
+
+.right
+ call ComposeMail_GetCursorPosition
+ and a
+ jr nz, .case_del_done_right
+ ld hl, SPRITEANIMSTRUCT_0C
+ add hl, bc
+ ld a, [hl]
+ cp $9
+ jr nc, .wrap_around_letter_right
+ inc [hl]
+ ret
+
+.wrap_around_letter_right
+ ld [hl], $0
+ ret
+
+.case_del_done_right
+ cp $3
+ jr nz, .wrap_around_command_right
+ xor a
+.wrap_around_command_right
+ ld e, a
+ add a
+ add e
+ ld hl, SPRITEANIMSTRUCT_0C
+ add hl, bc
+ ld [hl], a
+ ret
+
+.left
+ call ComposeMail_GetCursorPosition
+ and a
+ jr nz, .caps_del_done_left
+ ld hl, SPRITEANIMSTRUCT_0C
+ add hl, bc
+ ld a, [hl]
+ and a
+ jr z, .wrap_around_letter_left
+ dec [hl]
+ ret
+
+.wrap_around_letter_left
+ ld [hl], $9
+ ret
+
+.caps_del_done_left
+ cp $1
+ jr nz, .wrap_around_command_left
+ ld a, $4
+.wrap_around_command_left
+ dec a
+ dec a
+ ld e, a
+ add a
+ add e
+ ld hl, SPRITEANIMSTRUCT_0C
+ add hl, bc
+ ld [hl], a
+ ret
+
+.down
+ ld hl, SPRITEANIMSTRUCT_0D
+ add hl, bc
+ ld a, [hl]
+ cp $5
+ jr nc, .wrap_around_down
+ inc [hl]
+ ret
+
+.wrap_around_down
+ ld [hl], $0
+ ret
+
+.up
+ ld hl, SPRITEANIMSTRUCT_0D
+ add hl, bc
+ ld a, [hl]
+ and a
+ jr z, .wrap_around_up
+ dec [hl]
+ ret
+
+.wrap_around_up
+ ld [hl], $5
+ ret
+
+NamingScreen_PressedA_GetCursorCommand:
+ ld hl, wNamingScreenCursorObjectPointer
+ ld c, [hl]
+ inc hl
+ ld b, [hl]
+
+ComposeMail_GetCursorPosition:
+ ld hl, SPRITEANIMSTRUCT_0D
+ add hl, bc
+ ld a, [hl]
+ cp $5
+ jr nz, .letter
+ ld hl, SPRITEANIMSTRUCT_0C
+ add hl, bc
+ ld a, [hl]
+ cp $3
+ jr c, .case
+ cp $6
+ jr c, .del
+ ld a, $3
+ ret
+
+.case
+ ld a, $1
+ ret
+
+.del
+ ld a, $2
+ ret
+
+.letter
+ xor a
+ ret
+
+MailComposition_TryAddLastCharacter:
+ ld a, [wNamingScreenLastCharacter]
+ ld hl, Dakutens
+ cp "゙" ; $e5
+ jr z, .asm_1258b
+ ld hl, Handakutens
+ cp "゚" ; $e4
+ jp nz, MailComposition_TryAddCharacter
+
+.asm_1258b
+ ld a, [wNamingScreenCurNameLength]
+ and a
+ ret z
+ cp $11
+ jr nz, .asm_1259c
+ push hl
+ ld hl, wNamingScreenCurNameLength
+ dec [hl]
+ dec [hl]
+ jr .asm_125a1
+
+.asm_1259c
+ push hl
+ ld hl, wNamingScreenCurNameLength
+ dec [hl]
+
+.asm_125a1
+ call NamingScreen_GetTextCursorPosition
+ ld c, [hl]
+ pop hl
+.asm_125a6
+ ld a, [hli]
+ cp $ff
+ jp z, NamingScreen_AdvanceCursor_CheckEndOfString
+ cp c
+ jr z, .asm_125b2
+ inc hl
+ jr .asm_125a6
+
+.asm_125b2
+ ld a, [hl]
+ jp NamingScreen_LoadNextCharacter
+
+INCLUDE "data/text/mail_input_chars.asm"
diff --git a/engine/menus/save.asm b/engine/menus/save.asm
new file mode 100644
index 00000000..f8207204
--- /dev/null
+++ b/engine/menus/save.asm
@@ -0,0 +1,1092 @@
+SaveMenu:
+ call LoadStandardMenuHeader
+ lb de, 4, 0
+ farcall DisplayNormalContinueData
+ call SpeechTextbox
+ call UpdateSprites
+ farcall SaveMenu_CopyTilemapAtOnce
+ ld hl, WouldYouLikeToSaveTheGameText
+ call SaveTheGame_yesorno
+ jr nz, .refused
+ call AskOverwriteSaveFile
+ jr c, .refused
+ call PauseGameLogic
+ call SavingDontTurnOffThePower
+ call ResumeGameLogic
+ call ExitMenu
+ and a
+ ret
+
+.refused
+ call ExitMenu
+ call ReloadPalettes
+ farcall SaveMenu_CopyTilemapAtOnce
+ scf
+ ret
+
+SaveAfterLinkTrade:
+ call PauseGameLogic
+ farcall StageRTCTimeForSave
+ farcall BackupMysteryGift
+ call SavePokemonData
+ call SaveChecksum
+ call SaveBackupPokemonData
+ call SaveBackupChecksum
+ farcall BackupPartyMonMail
+ farcall SaveRTC
+ call ResumeGameLogic
+ ret
+
+ChangeBoxSaveGame:
+ push de
+ ld hl, ChangeBoxSaveText
+ call MenuTextbox
+ call YesNoBox
+ call ExitMenu
+ jr c, .refused
+ call AskOverwriteSaveFile
+ jr c, .refused
+ call PauseGameLogic
+ call SaveBox
+ pop de
+ ld a, e
+ ld [wCurBox], a
+ call LoadBox
+ call SavingDontTurnOffThePower
+ call ResumeGameLogic
+ and a
+ ret
+.refused
+ pop de
+ ret
+
+Link_SaveGame:
+ call AskOverwriteSaveFile
+ jr c, .refused
+ call PauseGameLogic
+ call SavingDontTurnOffThePower
+ call ResumeGameLogic
+ and a
+
+.refused
+ ret
+
+MoveMonWOMail_SaveGame:
+ call PauseGameLogic
+ push de
+ call SaveBox
+ pop de
+ ld a, e
+ ld [wCurBox], a
+ call LoadBox
+ call ResumeGameLogic
+ ret
+
+MoveMonWOMail_InsertMon_SaveGame:
+ call PauseGameLogic
+ push de
+ call SaveBox
+ pop de
+ ld a, e
+ ld [wCurBox], a
+ ld a, TRUE
+ ld [wSaveFileExists], a
+ farcall StageRTCTimeForSave
+ farcall BackupMysteryGift
+ call ValidateSave
+ call SaveOptions
+ call SavePlayerData
+ call SavePokemonData
+ call SaveChecksum
+ call ValidateBackupSave
+ call SaveBackupOptions
+ call SaveBackupPlayerData
+ call SaveBackupPokemonData
+ call SaveBackupChecksum
+ farcall BackupPartyMonMail
+ farcall SaveRTC
+ call LoadBox
+ call ResumeGameLogic
+ ld de, SFX_SAVE
+ call PlaySFX
+ ld c, 24
+ call DelayFrames
+ ret
+
+StartMoveMonWOMail_SaveGame:
+ ld hl, MoveMonWOMailSaveText
+ call MenuTextbox
+ call YesNoBox
+ call ExitMenu
+ jr c, .refused
+ call AskOverwriteSaveFile
+ jr c, .refused
+ call PauseGameLogic
+ call SavingDontTurnOffThePower
+ call ResumeGameLogic
+ and a
+ ret
+
+.refused
+ scf
+ ret
+
+PauseGameLogic:
+ ld a, TRUE
+ ld [wGameLogicPaused], a
+ ret
+
+ResumeGameLogic:
+ xor a ; FALSE
+ ld [wGameLogicPaused], a
+ ret
+
+AddHallOfFameEntry:
+ ld a, BANK(sHallOfFame)
+ call OpenSRAM
+ ld hl, sHallOfFame + HOF_LENGTH * (NUM_HOF_TEAMS - 1) - 1
+ ld de, sHallOfFame + HOF_LENGTH * NUM_HOF_TEAMS - 1
+ ld bc, HOF_LENGTH * (NUM_HOF_TEAMS - 1)
+.loop
+ ld a, [hld]
+ ld [de], a
+ dec de
+ dec bc
+ ld a, c
+ or b
+ jr nz, .loop
+ ld hl, wHallOfFamePokemonList
+ ld de, sHallOfFame
+ ld bc, wHallOfFamePokemonListEnd - wHallOfFamePokemonList + 1
+ call CopyBytes
+ call CloseSRAM
+ ret
+
+SaveGameData:
+ call _SaveGameData
+ ret
+
+AskOverwriteSaveFile:
+ ld a, [wSaveFileExists]
+ and a
+ jr z, .erase
+ call CompareLoadedAndSavedPlayerID
+ jr z, .yoursavefile
+ ld hl, AnotherSaveFileText
+ call SaveTheGame_yesorno
+ jr nz, .refused
+ jr .erase
+
+.yoursavefile
+ ld hl, AlreadyASaveFileText
+ call SaveTheGame_yesorno
+ jr nz, .refused
+ jr .ok
+
+.erase
+ call ErasePreviousSave
+
+.ok
+ and a
+ ret
+
+.refused
+ scf
+ ret
+
+SaveTheGame_yesorno:
+ ld b, BANK(WouldYouLikeToSaveTheGameText)
+ call MapTextbox
+ call LoadMenuTextbox
+ lb bc, 0, 7
+ call PlaceYesNoBox
+ ld a, [wMenuCursorY]
+ dec a
+ call CloseWindow
+ push af
+ call ReloadPalettes
+ pop af
+ and a
+ ret
+
+CompareLoadedAndSavedPlayerID:
+ ld a, BANK(sPlayerData)
+ call OpenSRAM
+ ld hl, sPlayerData + (wPlayerID - wPlayerData)
+ ld a, [hli]
+ ld c, [hl]
+ ld b, a
+ call CloseSRAM
+ ld a, [wPlayerID]
+ cp b
+ ret nz
+ ld a, [wPlayerID + 1]
+ cp c
+ ret
+
+SavingDontTurnOffThePower:
+ ; Prevent joypad interrupts
+ xor a
+ ldh [hJoypadReleased], a
+ ldh [hJoypadPressed], a
+ ldh [hJoypadSum], a
+ ldh [hJoypadDown], a
+ ; Save the text speed setting to the stack
+ ld a, [wOptions]
+ push af
+ ; Set the text speed to medium
+ ld a, TEXT_DELAY_MED
+ ld [wOptions], a
+ ; SAVING... DON'T TURN OFF THE POWER.
+ ld hl, SavingDontTurnOffThePowerText
+ call PrintText
+ ; Restore the text speed setting
+ pop af
+ ld [wOptions], a
+ ; Wait for 16 frames
+ ld c, 16
+ call DelayFrames
+ call _SaveGameData
+ ; wait 32 frames
+ ld c, 32
+ call DelayFrames
+ ; copy the original text speed setting to the stack
+ ld a, [wOptions]
+ push af
+ ; set text speed to medium
+ ld a, TEXT_DELAY_MED
+ ld [wOptions], a
+ ; <PLAYER> saved the game!
+ ld hl, SavedTheGameText
+ call PrintText
+ ; restore the original text speed setting
+ pop af
+ ld [wOptions], a
+ ld de, SFX_SAVE
+ call WaitPlaySFX
+ call WaitSFX
+ ; wait 30 frames
+ ld c, 30
+ call DelayFrames
+ ret
+
+_SaveGameData:
+ ld a, TRUE
+ ld [wSaveFileExists], a
+ farcall StageRTCTimeForSave
+ farcall BackupMysteryGift
+ call ValidateSave
+ call SaveOptions
+ call SavePlayerData
+ call SavePokemonData
+ call SaveBox
+ call SaveChecksum
+ call ValidateBackupSave
+ call SaveBackupOptions
+ call SaveBackupPlayerData
+ call SaveBackupPokemonData
+ call SaveBackupChecksum
+ call UpdateStackTop
+ farcall BackupPartyMonMail
+ farcall SaveRTC
+ ret
+
+UpdateStackTop:
+; sStackTop appears to be unused.
+; It could have been used to debug stack overflow during saving.
+ call FindStackTop
+ ld a, BANK(sStackTop)
+ call OpenSRAM
+ ld a, [sStackTop + 0]
+ ld e, a
+ ld a, [sStackTop + 1]
+ ld d, a
+ or e
+ jr z, .update
+ ld a, e
+ sub l
+ ld a, d
+ sbc h
+ jr c, .done
+
+.update
+ ld a, l
+ ld [sStackTop + 0], a
+ ld a, h
+ ld [sStackTop + 1], a
+
+.done
+ call CloseSRAM
+ ret
+
+FindStackTop:
+; Find the furthest point that sp has traversed to.
+; This is distinct from the current value of sp.
+ ld hl, wStackTop - $fc
+.loop
+ ld a, [hl]
+ or a
+ ret nz
+ inc hl
+ jr .loop
+
+ErasePreviousSave:
+ call EraseBoxes
+ call EraseHallOfFame
+ call EraseLinkBattleStats
+ call EraseMysteryGift
+ ld a, BANK(sStackTop)
+ call OpenSRAM
+ xor a
+ ld [sStackTop + 0], a
+ ld [sStackTop + 1], a
+ call CloseSRAM
+ ld a, $1
+ ld [wSavedAtLeastOnce], a
+ ret
+
+EraseLinkBattleStats:
+ ld a, BANK(sLinkBattleStats)
+ call OpenSRAM
+ ld hl, sLinkBattleStats
+ ld bc, sLinkBattleStatsEnd - sLinkBattleStats
+ xor a
+ call ByteFill
+ jp CloseSRAM
+
+EraseMysteryGift:
+ ld a, BANK(sBackupMysteryGiftItem)
+ call OpenSRAM
+ ld hl, sBackupMysteryGiftItem
+ ld bc, sBackupMysteryGiftItemEnd - sBackupMysteryGiftItem
+ xor a
+ call ByteFill
+ jp CloseSRAM
+
+EraseHallOfFame:
+ ld a, BANK(sHallOfFame)
+ call OpenSRAM
+ ld hl, sHallOfFame
+ ld bc, sHallOfFameEnd - sHallOfFame
+ xor a
+ call ByteFill
+ jp CloseSRAM
+
+ValidateSave:
+ ld a, BANK(sCheckValue1) ; aka BANK(sCheckValue2)
+ call OpenSRAM
+ ld a, SAVE_CHECK_VALUE_1
+ ld [sCheckValue1], a
+ ld a, SAVE_CHECK_VALUE_2
+ ld [sCheckValue2], a
+ jp CloseSRAM
+
+SaveOptions:
+ ld a, BANK(sOptions)
+ call OpenSRAM
+ ld hl, wOptions
+ ld de, sOptions
+ ld bc, wOptionsEnd - wOptions
+ call CopyBytes
+ ld a, [wOptions]
+ and $ff ^ (1 << NO_TEXT_SCROLL)
+ ld [sOptions], a
+ jp CloseSRAM
+
+SavePlayerData:
+ ld a, BANK(sPlayerData)
+ call OpenSRAM
+ ld hl, wPlayerData
+ ld de, sPlayerData
+ ld bc, wPlayerDataEnd - wPlayerData
+ call CopyBytes
+ ld hl, wCurMapData
+ ld de, sCurMapData
+ ld bc, wCurMapDataEnd - wCurMapData
+ call CopyBytes
+ jp CloseSRAM
+
+SavePokemonData:
+ ld a, BANK(sPokemonData)
+ call OpenSRAM
+ ld hl, wPokemonData
+ ld de, sPokemonData
+ ld bc, wPokemonDataEnd - wPokemonData
+ call CopyBytes
+ call CloseSRAM
+ ret
+
+SaveBox:
+ call GetBoxAddress
+ call SaveBoxAddress
+ ret
+
+SaveChecksum:
+ ld hl, sGameData
+ ld bc, sGameDataEnd - sGameData
+ ld a, BANK(sGameData)
+ call OpenSRAM
+ call Checksum
+ ld a, e
+ ld [sChecksum + 0], a
+ ld a, d
+ ld [sChecksum + 1], a
+ call CloseSRAM
+ ret
+
+ValidateBackupSave:
+ ld a, BANK(sBackupCheckValue1) ; aka BANK(sBackupCheckValue2)
+ call OpenSRAM
+ ld a, SAVE_CHECK_VALUE_1
+ ld [sBackupCheckValue1], a
+ ld a, SAVE_CHECK_VALUE_2
+ ld [sBackupCheckValue2], a
+ call CloseSRAM
+ ret
+
+SaveBackupOptions:
+ ld a, BANK(sBackupOptions)
+ call OpenSRAM
+ ld hl, wOptions
+ ld de, sBackupOptions
+ ld bc, wOptionsEnd - wOptions
+ call CopyBytes
+ call CloseSRAM
+ ret
+
+SaveBackupPlayerData:
+ ld a, BANK(sBackupPlayerData3)
+ call OpenSRAM
+ ld hl, wPlayerData3
+ ld de, sBackupPlayerData3
+ ld bc, wPlayerData3End - wPlayerData3
+ call CopyBytes
+ ld a, BANK(sBackupPlayerData1)
+ call OpenSRAM
+ ld hl, wPlayerData1
+ ld de, sBackupPlayerData1
+ ld bc, wPlayerData1End - wPlayerData1
+ call CopyBytes
+ ld a, BANK(sBackupPlayerData2)
+ call OpenSRAM
+ ld hl, wPlayerData2
+ ld de, sBackupPlayerData2
+ ld bc, wPlayerData2End - wPlayerData2
+ call CopyBytes
+ ld a, BANK(sBackupCurMapData)
+ call OpenSRAM
+ ld hl, wCurMapData
+ ld de, sBackupCurMapData
+ ld bc, wCurMapDataEnd - wCurMapData
+ call CopyBytes
+ call CloseSRAM
+ ret
+
+SaveBackupPokemonData:
+ ld a, BANK(sBackupPokemonData)
+ call OpenSRAM
+ ld hl, wPokemonData
+ ld de, sBackupPokemonData
+ ld bc, wPokemonDataEnd - wPokemonData
+ call CopyBytes
+ call CloseSRAM
+ ret
+
+SaveBackupChecksum:
+ ld a, BANK(sBackupPlayerData3)
+ call OpenSRAM
+ ld hl, sBackupPlayerData3
+ ld bc, wPlayerData3End - wPlayerData3
+ call Checksum
+ push de
+ ld hl, sBackupPokemonData
+ ld bc, wPokemonDataEnd - wPokemonData
+ call Checksum
+ pop hl
+ add hl, de
+ ld a, BANK(sBackupPlayerData1)
+ call OpenSRAM
+ push hl
+ ld hl, sBackupPlayerData1
+ ld bc, wPlayerData1End - wPlayerData1
+ call Checksum
+ pop hl
+ add hl, de
+ ld a, BANK(sBackupPlayerData2)
+ call OpenSRAM
+ push hl
+ ld hl, sBackupPlayerData2
+ ld bc, wPlayerData2End - wPlayerData2
+ call Checksum
+ pop hl
+ add hl, de
+ ld a, BANK(sBackupCurMapData)
+ call OpenSRAM
+ push hl
+ ld hl, sBackupCurMapData
+ ld bc, wCurMapDataEnd - wCurMapData
+ call Checksum
+ pop hl
+ add hl, de
+ ld a, l
+ ld [sBackupChecksum + 0], a
+ ld a, h
+ ld [sBackupChecksum + 1], a
+ call CloseSRAM
+ ret
+
+TryLoadSaveFile:
+ call VerifyChecksum
+ jr nz, .backup
+ call LoadPlayerData
+ call LoadPokemonData
+ call LoadBox
+ farcall RestorePartyMonMail
+ farcall RestoreMysteryGift
+ call ValidateBackupSave
+ call SaveBackupOptions
+ call SaveBackupPlayerData
+ call SaveBackupPokemonData
+ call SaveBackupChecksum
+ and a
+ ret
+
+.backup
+ call VerifyBackupChecksum
+ jr nz, .corrupt
+ call LoadBackupPlayerData
+ call LoadBackupPokemonData
+ call LoadBox
+ farcall RestorePartyMonMail
+ farcall RestoreMysteryGift
+ call ValidateSave
+ call SaveOptions
+ call SavePlayerData
+ call SavePokemonData
+ call SaveChecksum
+ and a
+ ret
+
+.corrupt
+ ld a, [wOptions]
+ push af
+ set NO_TEXT_SCROLL, a
+ ld [wOptions], a
+ ld hl, SaveFileCorruptedText
+ call PrintText
+ pop af
+ ld [wOptions], a
+ scf
+ ret
+
+TryLoadSaveData:
+ xor a ; FALSE
+ ld [wSaveFileExists], a
+ call CheckPrimarySaveFile
+ ld a, [wSaveFileExists]
+ and a
+ jr z, .backup
+
+ ld a, BANK(sPlayerData)
+ call OpenSRAM
+ ld hl, sPlayerData + wStartDay - wPlayerData
+ ld de, wStartDay
+ ld bc, $e
+ call CopyBytes
+ call CloseSRAM
+ ret
+
+.backup
+ call CheckBackupSaveFile
+ ld a, [wSaveFileExists]
+ and a
+ jr z, .corrupt
+
+ ld a, BANK(sBackupPlayerData1)
+ call OpenSRAM
+ ld hl, sBackupPlayerData1 + wStartDay - wPlayerData
+ ld de, wStartDay
+ ld bc, $e
+ call CopyBytes
+ call CloseSRAM
+ ret
+
+.corrupt
+ ld hl, DefaultOptions
+ ld de, wOptions
+ ld bc, wOptionsEnd - wOptions
+ call CopyBytes
+ call ClearClock
+ ret
+
+INCLUDE "data/default_options.asm"
+
+CheckPrimarySaveFile:
+ ld a, BANK(sCheckValue1) ; aka BANK(sCheckValue2)
+ call OpenSRAM
+ ld a, [sCheckValue1]
+ cp SAVE_CHECK_VALUE_1
+ jr nz, .nope
+ ld a, [sCheckValue2]
+ cp SAVE_CHECK_VALUE_2
+ jr nz, .nope
+ ld hl, sOptions
+ ld de, wOptions
+ ld bc, wOptionsEnd - wOptions
+ call CopyBytes
+ call CloseSRAM
+ call CheckTextDelay
+ ld a, TRUE
+ ld [wSaveFileExists], a
+
+.nope
+ call CloseSRAM
+ ret
+
+CheckBackupSaveFile:
+ ld a, BANK(sBackupCheckValue1) ; aka BANK(sBackupCheckValue2)
+ call OpenSRAM
+ ld a, [sBackupCheckValue1]
+ cp SAVE_CHECK_VALUE_1
+ jr nz, .nope
+ ld a, [sBackupCheckValue2]
+ cp SAVE_CHECK_VALUE_2
+ jr nz, .nope
+ ld hl, sBackupOptions
+ ld de, wOptions
+ ld bc, wOptionsEnd - wOptions
+ call CopyBytes
+ call CheckTextDelay
+ ld a, $2
+ ld [wSaveFileExists], a
+
+.nope
+ call CloseSRAM
+ ret
+
+CheckTextDelay:
+; Fix options if text delay is invalid
+ ld hl, wTextboxFlags
+ res NO_TEXT_DELAY_F, [hl]
+ ld a, [wOptions]
+ and TEXT_DELAY_MASK
+ cp TEXT_DELAY_FAST
+ ret z
+ cp TEXT_DELAY_MED
+ ret z
+ cp TEXT_DELAY_SLOW
+ ret z
+ ld a, [wOptions]
+ and $ff ^ TEXT_DELAY_MASK
+ or (1 << FAST_TEXT_DELAY_F) | (1 << NO_TEXT_DELAY_F)
+ ld [wOptions], a
+ ret
+
+LoadPlayerData:
+ ld a, BANK(sPlayerData)
+ call OpenSRAM
+ ld hl, sPlayerData
+ ld de, wPlayerData
+ ld bc, wPlayerDataEnd - wPlayerData
+ call CopyBytes
+ ld hl, sCurMapData
+ ld de, wCurMapData
+ ld bc, wCurMapDataEnd - wCurMapData
+ call CopyBytes
+ call CloseSRAM
+ ret
+
+LoadPokemonData:
+ ld a, BANK(sPokemonData)
+ call OpenSRAM
+ ld hl, sPokemonData
+ ld de, wPokemonData
+ ld bc, wPokemonDataEnd - wPokemonData
+ call CopyBytes
+ call CloseSRAM
+ ret
+
+LoadBox:
+ call GetBoxAddress
+ call LoadBoxAddress
+ ret
+
+VerifyChecksum:
+ ld hl, sGameData
+ ld bc, sGameDataEnd - sGameData
+ ld a, BANK(sGameData)
+ call OpenSRAM
+ call Checksum
+ ld a, [sChecksum + 0]
+ cp e
+ jr nz, .fail
+ ld a, [sChecksum + 1]
+ cp d
+.fail
+ push af
+ call CloseSRAM
+ pop af
+ ret
+
+LoadBackupPlayerData:
+ ld a, BANK(sBackupPlayerData3)
+ call OpenSRAM
+ ld hl, sBackupPlayerData3
+ ld de, wPlayerData3
+ ld bc, wPlayerData3End - wPlayerData3
+ call CopyBytes
+
+ ld a, BANK(sBackupPlayerData1)
+ call OpenSRAM
+ ld hl, sBackupPlayerData1
+ ld de, wPlayerData1
+ ld bc, wPlayerData1End - wPlayerData1
+ call CopyBytes
+
+ ld a, BANK(sBackupPlayerData2)
+ call OpenSRAM
+ ld hl, sBackupPlayerData2
+ ld de, wPlayerData2
+ ld bc, wPlayerData2End - wPlayerData2
+ call CopyBytes
+
+ ld a, BANK(sBackupCurMapData)
+ call OpenSRAM
+ ld hl, sBackupCurMapData
+ ld de, wCurMapData
+ ld bc, wCurMapDataEnd - wCurMapData
+ call CopyBytes
+ call CloseSRAM
+ ret
+
+LoadBackupPokemonData:
+ ld a, BANK(sBackupPokemonData)
+ call OpenSRAM
+ ld hl, sBackupPokemonData
+ ld de, wPokemonData
+ ld bc, wPokemonDataEnd - wPokemonData
+ call CopyBytes
+ call CloseSRAM
+ ret
+
+VerifyBackupChecksum:
+ ld a, BANK(sBackupPokemonData)
+ call OpenSRAM
+ ld hl, sBackupPokemonData
+ ld bc, wPokemonDataEnd - wPokemonData
+ call Checksum
+ push de
+
+ ld hl, sBackupPlayerData3
+ ld bc, wPlayerData3End - wPlayerData3
+ call Checksum
+ pop hl
+ add hl, de
+
+ ld a, BANK(sBackupPlayerData1)
+ call OpenSRAM
+ push hl
+ ld hl, sBackupPlayerData1
+ ld bc, wPlayerData1End - wPlayerData1
+ call Checksum
+ pop hl
+ add hl, de
+
+ ld a, BANK(sBackupPlayerData2)
+ call OpenSRAM
+ push hl
+ ld hl, sBackupPlayerData2
+ ld bc, wPlayerData2End - wPlayerData2
+ call Checksum
+ pop hl
+ add hl, de
+
+ ld a, BANK(sBackupCurMapData)
+ call OpenSRAM
+ push hl
+ ld hl, sBackupCurMapData
+ ld bc, wCurMapDataEnd - wCurMapData
+ call Checksum
+ pop hl
+ add hl, de
+ ld d, h
+ ld e, l
+ ld a, [sBackupChecksum + 0]
+ cp e
+ jr nz, .fail
+ ld a, [sBackupChecksum + 1]
+ cp d
+.fail
+ push af
+ call CloseSRAM
+ pop af
+ ret
+
+GetBoxAddress:
+ ld a, [wCurBox]
+ cp NUM_BOXES
+ jr c, .ok
+ xor a
+ ld [wCurBox], a
+
+.ok
+ ld e, a
+ ld d, 0
+ ld hl, BoxAddresses
+rept 5
+ add hl, de
+endr
+ ld a, [hli]
+ push af
+ ld a, [hli]
+ ld e, a
+ ld a, [hli]
+ ld d, a
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ pop af
+ ret
+
+SaveBoxAddress:
+; Save box via wBoxPartialData.
+; We do this in three steps because the size of wBoxPartialData is less than
+; the size of sBox.
+ push hl
+; Load the first part of the active box.
+ push af
+ push de
+ ld a, BANK(sBox)
+ call OpenSRAM
+ ld hl, sBox
+ ld de, wBoxPartialData
+ ld bc, (wBoxPartialDataEnd - wBoxPartialData)
+ call CopyBytes
+ call CloseSRAM
+ pop de
+ pop af
+; Save it to the target box.
+ push af
+ push de
+ call OpenSRAM
+ ld hl, wBoxPartialData
+ ld bc, (wBoxPartialDataEnd - wBoxPartialData)
+ call CopyBytes
+ call CloseSRAM
+
+; Load the second part of the active box.
+ ld a, BANK(sBox)
+ call OpenSRAM
+ ld hl, sBox + (wBoxPartialDataEnd - wBoxPartialData)
+ ld de, wBoxPartialData
+ ld bc, (wBoxPartialDataEnd - wBoxPartialData)
+ call CopyBytes
+ call CloseSRAM
+ pop de
+ pop af
+
+ ld hl, (wBoxPartialDataEnd - wBoxPartialData)
+ add hl, de
+ ld e, l
+ ld d, h
+; Save it to the next part of the target box.
+ push af
+ push de
+ call OpenSRAM
+ ld hl, wBoxPartialData
+ ld bc, (wBoxPartialDataEnd - wBoxPartialData)
+ call CopyBytes
+ call CloseSRAM
+
+; Load the third and final part of the active box.
+ ld a, BANK(sBox)
+ call OpenSRAM
+ ld hl, sBox + (wBoxPartialDataEnd - wBoxPartialData) * 2
+ ld de, wBoxPartialData
+ ld bc, sBoxEnd - (sBox + (wBoxPartialDataEnd - wBoxPartialData) * 2) ; $8e
+ call CopyBytes
+ call CloseSRAM
+ pop de
+ pop af
+
+ ld hl, (wBoxPartialDataEnd - wBoxPartialData)
+ add hl, de
+ ld e, l
+ ld d, h
+; Save it to the final part of the target box.
+ call OpenSRAM
+ ld hl, wBoxPartialData
+ ld bc, sBoxEnd - (sBox + (wBoxPartialDataEnd - wBoxPartialData) * 2) ; $8e
+ call CopyBytes
+ call CloseSRAM
+
+ pop hl
+ ret
+
+LoadBoxAddress:
+; Load box via wBoxPartialData.
+; We do this in three steps because the size of wBoxPartialData is less than
+; the size of sBox.
+ push hl
+ ld l, e
+ ld h, d
+; Load part 1
+ push af
+ push hl
+ call OpenSRAM
+ ld de, wBoxPartialData
+ ld bc, (wBoxPartialDataEnd - wBoxPartialData)
+ call CopyBytes
+ call CloseSRAM
+ ld a, BANK(sBox)
+ call OpenSRAM
+ ld hl, wBoxPartialData
+ ld de, sBox
+ ld bc, (wBoxPartialDataEnd - wBoxPartialData)
+ call CopyBytes
+ call CloseSRAM
+ pop hl
+ pop af
+
+ ld de, (wBoxPartialDataEnd - wBoxPartialData)
+ add hl, de
+; Load part 2
+ push af
+ push hl
+ call OpenSRAM
+ ld de, wBoxPartialData
+ ld bc, (wBoxPartialDataEnd - wBoxPartialData)
+ call CopyBytes
+ call CloseSRAM
+ ld a, BANK(sBox)
+ call OpenSRAM
+ ld hl, wBoxPartialData
+ ld de, sBox + (wBoxPartialDataEnd - wBoxPartialData)
+ ld bc, (wBoxPartialDataEnd - wBoxPartialData)
+ call CopyBytes
+ call CloseSRAM
+ pop hl
+ pop af
+; Load part 3
+ ld de, (wBoxPartialDataEnd - wBoxPartialData)
+ add hl, de
+ call OpenSRAM
+ ld de, wBoxPartialData
+ ld bc, sBoxEnd - (sBox + (wBoxPartialDataEnd - wBoxPartialData) * 2) ; $8e
+ call CopyBytes
+ call CloseSRAM
+ ld a, BANK(sBox)
+ call OpenSRAM
+ ld hl, wBoxPartialData
+ ld de, sBox + (wBoxPartialDataEnd - wBoxPartialData) * 2
+ ld bc, sBoxEnd - (sBox + (wBoxPartialDataEnd - wBoxPartialData) * 2) ; $8e
+ call CopyBytes
+ call CloseSRAM
+
+ pop hl
+ ret
+
+EraseBoxes:
+ ld hl, BoxAddresses
+ ld c, NUM_BOXES
+.next
+ push bc
+ ld a, [hli]
+ call OpenSRAM
+ ld a, [hli]
+ ld e, a
+ ld a, [hli]
+ ld d, a
+ xor a
+ ld [de], a
+ inc de
+ ld a, -1
+ ld [de], a
+ inc de
+ ld bc, sBoxEnd - (sBox + 2)
+.clear
+ xor a
+ ld [de], a
+ inc de
+ dec bc
+ ld a, b
+ or c
+ jr nz, .clear
+ ld a, [hli]
+ ld e, a
+ ld a, [hli]
+ ld d, a
+ ld a, -1
+ ld [de], a
+ inc de
+ xor a
+ ld [de], a
+ call CloseSRAM
+ pop bc
+ dec c
+ jr nz, .next
+ ret
+
+BoxAddresses:
+; dbww bank, address, address
+ dbww BANK(sBox1), sBox1, sBox1End
+ dbww BANK(sBox2), sBox2, sBox2End
+ dbww BANK(sBox3), sBox3, sBox3End
+ dbww BANK(sBox4), sBox4, sBox4End
+ dbww BANK(sBox5), sBox5, sBox5End
+ dbww BANK(sBox6), sBox6, sBox6End
+ dbww BANK(sBox7), sBox7, sBox7End
+ dbww BANK(sBox8), sBox8, sBox8End
+ dbww BANK(sBox9), sBox9, sBox9End
+ dbww BANK(sBox10), sBox10, sBox10End
+ dbww BANK(sBox11), sBox11, sBox11End
+ dbww BANK(sBox12), sBox12, sBox12End
+ dbww BANK(sBox13), sBox13, sBox13End
+ dbww BANK(sBox14), sBox14, sBox14End
+
+Checksum:
+ ld de, 0
+.loop
+ ld a, [hli]
+ add e
+ ld e, a
+ ld a, 0
+ adc d
+ ld d, a
+ dec bc
+ ld a, b
+ or c
+ jr nz, .loop
+ ret
+
+WouldYouLikeToSaveTheGameText:
+ text_far _WouldYouLikeToSaveTheGameText
+ text_end
+
+SavingDontTurnOffThePowerText:
+ text_far _SavingDontTurnOffThePowerText
+ text_end
+
+SavedTheGameText:
+ text_far _SavedTheGameText
+ text_end
+
+AlreadyASaveFileText:
+ text_far _AlreadyASaveFileText
+ text_end
+
+AnotherSaveFileText:
+ text_far _AnotherSaveFileText
+ text_end
+
+SaveFileCorruptedText:
+ text_far _SaveFileCorruptedText
+ text_end
+
+ChangeBoxSaveText:
+ text_far _ChangeBoxSaveText
+ text_end
+
+MoveMonWOMailSaveText:
+ text_far _MoveMonWOMailSaveText
+ text_end
diff --git a/engine/menus/scrolling_menu.asm b/engine/menus/scrolling_menu.asm
new file mode 100644
index 00000000..f051e71c
--- /dev/null
+++ b/engine/menus/scrolling_menu.asm
@@ -0,0 +1,519 @@
+_InitScrollingMenu::
+ xor a
+ ld [wMenuJoypad], a
+ ldh [hBGMapMode], a
+ inc a
+ ldh [hInMenu], a
+ call InitScrollingMenuCursor
+ call ScrollingMenu_InitFlags
+ call ScrollingMenu_ValidateSwitchItem
+ call ScrollingMenu_InitDisplay
+ call ApplyTilemap
+ xor a
+ ldh [hBGMapMode], a
+ ret
+
+_ScrollingMenu::
+.loop
+ call ScrollingMenuJoyAction
+ jp c, .exit
+ call z, .zero
+ jr .loop
+
+.exit
+ call MenuClickSound
+ ld [wMenuJoypad], a
+ ld a, 0
+ ldh [hInMenu], a
+ ret
+
+.zero
+ call ScrollingMenu_InitDisplay
+ ld a, 1
+ ldh [hBGMapMode], a
+ ld c, 3
+ call DelayFrames
+ xor a
+ ldh [hBGMapMode], a
+ ret
+
+ScrollingMenu_InitDisplay:
+ xor a
+ ldh [hBGMapMode], a
+ ld hl, wOptions
+ ld a, [hl]
+ push af
+ set NO_TEXT_SCROLL, [hl]
+ call ScrollingMenu_UpdateDisplay
+ call ScrollingMenu_PlaceCursor
+ call ScrollingMenu_CheckCallFunction3
+ pop af
+ ld [wOptions], a
+ ret
+
+ScrollingMenuJoyAction:
+.loop
+ call ScrollingMenuJoypad
+ ldh a, [hJoyLast]
+ and D_PAD
+ ld b, a
+ ldh a, [hJoyPressed]
+ and BUTTONS
+ or b
+ bit A_BUTTON_F, a
+ jp nz, .a_button
+ bit B_BUTTON_F, a
+ jp nz, .b_button
+ bit SELECT_F, a
+ jp nz, .select
+ bit START_F, a
+ jp nz, .start
+ bit D_RIGHT_F, a
+ jp nz, .d_right
+ bit D_LEFT_F, a
+ jp nz, .d_left
+ bit D_UP_F, a
+ jp nz, .d_up
+ bit D_DOWN_F, a
+ jp nz, .d_down
+ jr .loop
+
+.unreferenced ; unused
+ ld a, -1
+ and a
+ ret
+
+.a_button
+ call PlaceHollowCursor
+ ld a, [wMenuCursorY]
+ dec a
+ call ScrollingMenu_GetListItemCoordAndFunctionArgs
+ ld a, [wMenuSelection]
+ ld [wCurItem], a
+ ld a, [wMenuSelectionQuantity]
+ ld [wItemQuantityBuffer], a
+ call ScrollingMenu_GetCursorPosition
+ dec a
+ ld [wScrollingMenuCursorPosition], a
+ ld [wCurItemQuantity], a
+ ld a, [wMenuSelection]
+ cp -1
+ jr z, .b_button
+ ld a, A_BUTTON
+ scf
+ ret
+
+.b_button
+ ld a, B_BUTTON
+ scf
+ ret
+
+.select
+ ld a, [wMenuDataFlags]
+ bit 7, a
+ jp z, xor_a_dec_a
+ ld a, [wMenuCursorY]
+ dec a
+ call ScrollingMenu_GetListItemCoordAndFunctionArgs
+ ld a, [wMenuSelection]
+ cp -1
+ jp z, xor_a_dec_a
+ call ScrollingMenu_GetCursorPosition
+ dec a
+ ld [wScrollingMenuCursorPosition], a
+ ld a, SELECT
+ scf
+ ret
+
+.start
+ ld a, [wMenuDataFlags]
+ bit 6, a
+ jp z, xor_a_dec_a
+ ld a, START
+ scf
+ ret
+
+.d_left
+ ld hl, w2DMenuFlags2
+ bit 7, [hl]
+ jp z, xor_a_dec_a
+ ld a, [wMenuDataFlags]
+ bit 3, a
+ jp z, xor_a_dec_a
+ ld a, D_LEFT
+ scf
+ ret
+
+.d_right
+ ld hl, w2DMenuFlags2
+ bit 7, [hl]
+ jp z, xor_a_dec_a
+ ld a, [wMenuDataFlags]
+ bit 2, a
+ jp z, xor_a_dec_a
+ ld a, D_RIGHT
+ scf
+ ret
+
+.d_up
+ ld hl, w2DMenuFlags2
+ bit 7, [hl]
+ jp z, xor_a
+ ld hl, wMenuScrollPosition
+ ld a, [hl]
+ and a
+ jr z, .xor_dec_up
+ dec [hl]
+ jp xor_a
+
+.xor_dec_up
+ jp xor_a_dec_a
+
+.d_down
+ ld hl, w2DMenuFlags2
+ bit 7, [hl]
+ jp z, xor_a
+ ld hl, wMenuScrollPosition
+ ld a, [wMenuData_ScrollingMenuHeight]
+ add [hl]
+ ld b, a
+ ld a, [wScrollingMenuListSize]
+ cp b
+ jr c, .xor_dec_down
+ inc [hl]
+ jp xor_a
+
+.xor_dec_down
+ jp xor_a_dec_a
+
+ScrollingMenu_GetCursorPosition:
+ ld a, [wMenuScrollPosition]
+ ld c, a
+ ld a, [wMenuCursorY]
+ add c
+ ld c, a
+ ret
+
+ScrollingMenu_ClearLeftColumn:
+ call MenuBoxCoord2Tile
+ ld de, SCREEN_WIDTH
+ add hl, de
+ ld de, 2 * SCREEN_WIDTH
+ ld a, [wMenuData_ScrollingMenuHeight]
+.loop
+ ld [hl], " "
+ add hl, de
+ dec a
+ jr nz, .loop
+ ret
+
+InitScrollingMenuCursor:
+ ld hl, wMenuData_ItemsPointerAddr
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld a, [wMenuData_ItemsPointerBank]
+ call GetFarByte
+ ld [wScrollingMenuListSize], a
+ ld a, [wMenuData_ScrollingMenuHeight]
+ ld c, a
+ ld a, [wMenuScrollPosition]
+ add c
+ ld c, a
+ ld a, [wScrollingMenuListSize]
+ inc a
+ cp c
+ jr nc, .skip
+ ld a, [wMenuData_ScrollingMenuHeight]
+ ld c, a
+ ld a, [wScrollingMenuListSize]
+ inc a
+ sub c
+ jr nc, .store
+ xor a
+
+.store
+ ld [wMenuScrollPosition], a
+
+.skip
+ ld a, [wMenuScrollPosition]
+ ld c, a
+ ld a, [wMenuCursorBuffer]
+ add c
+ ld b, a
+ ld a, [wScrollingMenuListSize]
+ inc a
+ cp b
+ jr c, .wrap
+ jr nc, .done
+
+.wrap
+ xor a
+ ld [wMenuScrollPosition], a
+ ld a, $1
+ ld [wMenuCursorBuffer], a
+
+.done
+ ret
+
+ScrollingMenu_InitFlags:
+ ld a, [wMenuDataFlags]
+ ld c, a
+ ld a, [wScrollingMenuListSize]
+ ld b, a
+ ld a, [wMenuBorderTopCoord]
+ add 1
+ ld [w2DMenuCursorInitY], a
+ ld a, [wMenuBorderLeftCoord]
+ add 0
+ ld [w2DMenuCursorInitX], a
+ ld a, [wMenuData_ScrollingMenuHeight]
+ cp b
+ jr c, .no_extra_row
+ jr z, .no_extra_row
+ ld a, b
+ inc a
+.no_extra_row
+ ld [w2DMenuNumRows], a
+ ld a, 1
+ ld [w2DMenuNumCols], a
+ ld a, $8c
+ bit 2, c
+ jr z, .skip_set_0
+ set 0, a
+
+.skip_set_0
+ bit 3, c
+ jr z, .skip_set_1
+ set 1, a
+
+.skip_set_1
+ ld [w2DMenuFlags1], a
+ xor a
+ ld [w2DMenuFlags2], a
+ ld a, $20
+ ld [w2DMenuCursorOffsets], a
+ ld a, A_BUTTON | B_BUTTON | D_UP | D_DOWN
+ bit 7, c
+ jr z, .disallow_select
+ add SELECT
+
+.disallow_select
+ bit 6, c
+ jr z, .disallow_start
+ add START
+
+.disallow_start
+ ld [wMenuJoypadFilter], a
+ ld a, [w2DMenuNumRows]
+ ld b, a
+ ld a, [wMenuCursorBuffer]
+ and a
+ jr z, .reset_cursor
+ cp b
+ jr z, .cursor_okay
+ jr c, .cursor_okay
+
+.reset_cursor
+ ld a, 1
+
+.cursor_okay
+ ld [wMenuCursorY], a
+ ld a, 1
+ ld [wMenuCursorX], a
+ xor a
+ ld [wCursorCurrentTile], a
+ ld [wCursorCurrentTile + 1], a
+ ld [wCursorOffCharacter], a
+ ret
+
+ScrollingMenu_ValidateSwitchItem:
+ ld a, [wScrollingMenuListSize]
+ ld c, a
+ ld a, [wSwitchItem]
+ and a
+ jr z, .done
+ dec a
+ cp c
+ jr c, .done
+ xor a
+ ld [wSwitchItem], a
+
+.done
+ ret
+
+ScrollingMenu_UpdateDisplay:
+ call ClearWholeMenuBox
+ ld a, [wMenuDataFlags]
+ bit 4, a ; place arrows
+ jr z, .okay
+ ld a, [wMenuScrollPosition]
+ and a
+ jr z, .okay
+ ld a, [wMenuBorderTopCoord]
+ ld b, a
+ ld a, [wMenuBorderRightCoord]
+ ld c, a
+ call Coord2Tile
+ ld [hl], "▲"
+
+.okay
+ call MenuBoxCoord2Tile
+ ld bc, SCREEN_WIDTH + 1
+ add hl, bc
+ ld a, [wMenuData_ScrollingMenuHeight]
+ ld b, a
+ ld c, $0
+.loop
+ ld a, [wMenuScrollPosition]
+ add c
+ ld [wScrollingMenuCursorPosition], a
+ ld a, c
+ call ScrollingMenu_GetListItemCoordAndFunctionArgs
+ ld a, [wMenuSelection]
+ cp -1
+ jr z, .cancel
+ push bc
+ push hl
+ call ScrollingMenu_CallFunctions1and2
+ pop hl
+ ld bc, 2 * SCREEN_WIDTH
+ add hl, bc
+ pop bc
+ inc c
+ ld a, c
+ cp b
+ jr nz, .loop
+ ld a, [wMenuDataFlags]
+ bit 4, a ; place arrows
+ jr z, .done
+ ld a, [wMenuBorderBottomCoord]
+ ld b, a
+ ld a, [wMenuBorderRightCoord]
+ ld c, a
+ call Coord2Tile
+ ld [hl], "▼"
+
+.done
+ ret
+
+.cancel
+ ld a, [wMenuDataFlags]
+ bit 0, a ; call function on cancel
+ jr nz, .call_function
+ ld de, .string_24787
+ call PlaceString
+ ret
+
+.string_24787
+ db "CANCEL@"
+
+.call_function
+ ld d, h
+ ld e, l
+ ld hl, wMenuData_ScrollingMenuFunction1
+ jp CallPointerAt
+
+ScrollingMenu_CallFunctions1and2:
+ push hl
+ ld d, h
+ ld e, l
+ ld hl, wMenuData_ScrollingMenuFunction1
+ call CallPointerAt
+ pop hl
+ ld a, [wMenuData_ScrollingMenuWidth]
+ and a
+ jr z, .done
+ ld e, a
+ ld d, $0
+ add hl, de
+ ld d, h
+ ld e, l
+ ld hl, wMenuData_ScrollingMenuFunction2
+ call CallPointerAt
+
+.done
+ ret
+
+ScrollingMenu_PlaceCursor:
+ ld a, [wSwitchItem]
+ and a
+ jr z, .done
+ ld b, a
+ ld a, [wMenuScrollPosition]
+ cp b
+ jr nc, .done
+ ld c, a
+ ld a, [wMenuData_ScrollingMenuHeight]
+ add c
+ cp b
+ jr c, .done
+ ld a, b
+ sub c
+ dec a
+ add a
+ add $1
+ ld c, a
+ ld a, [wMenuBorderTopCoord]
+ add c
+ ld b, a
+ ld a, [wMenuBorderLeftCoord]
+ add $0
+ ld c, a
+ call Coord2Tile
+ ld [hl], "▷"
+
+.done
+ ret
+
+ScrollingMenu_CheckCallFunction3:
+ ld a, [wMenuDataFlags]
+ bit 5, a ; call function 3
+ ret z
+ bit 1, a ; call function 3 if not switching items
+ jr z, .call
+ ld a, [wSwitchItem]
+ and a
+ ret nz
+
+.call
+ ld a, [wMenuCursorY]
+ dec a
+ call ScrollingMenu_GetListItemCoordAndFunctionArgs
+ ld hl, wMenuData_ScrollingMenuFunction3
+ call CallPointerAt
+ ret
+
+ScrollingMenu_GetListItemCoordAndFunctionArgs:
+ push de
+ push hl
+ ld e, a
+ ld a, [wMenuScrollPosition]
+ add e
+ ld e, a
+ ld d, $0
+ ld hl, wMenuData_ItemsPointerAddr
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ inc hl ; items
+ ld a, [wMenuData_ScrollingMenuItemFormat]
+ cp SCROLLINGMENU_ITEMS_NORMAL
+ jr z, .got_spacing
+ cp SCROLLINGMENU_ITEMS_QUANTITY
+ jr z, .pointless_jump
+.pointless_jump
+ add hl, de
+.got_spacing
+ add hl, de
+ ld a, [wMenuData_ItemsPointerBank]
+ call GetFarByte
+ ld [wMenuSelection], a
+ ld [wCurItem], a
+ inc hl
+ ld a, [wMenuData_ItemsPointerBank]
+ call GetFarByte
+ ld [wMenuSelectionQuantity], a
+ pop hl
+ pop de
+ ret
diff --git a/engine/menus/start_menu.asm b/engine/menus/start_menu.asm
new file mode 100644
index 00000000..dbadf637
--- /dev/null
+++ b/engine/menus/start_menu.asm
@@ -0,0 +1,541 @@
+; StartMenu.Items indexes
+ const_def
+ const STARTMENUITEM_POKEDEX ; 0
+ const STARTMENUITEM_POKEMON ; 1
+ const STARTMENUITEM_PACK ; 2
+ const STARTMENUITEM_STATUS ; 3
+ const STARTMENUITEM_SAVE ; 4
+ const STARTMENUITEM_OPTION ; 5
+ const STARTMENUITEM_EXIT ; 6
+ const STARTMENUITEM_POKEGEAR ; 7
+ const STARTMENUITEM_QUIT ; 8
+
+StartMenu::
+ call ClearWindowData
+
+ ld de, SFX_MENU
+ call PlaySFX
+
+ farcall ReanchorBGMap_NoOAMUpdate
+
+ ld hl, wStatusFlags2
+ bit STATUSFLAGS2_BUG_CONTEST_TIMER_F, [hl]
+ ld hl, .MenuHeader
+ jr z, .GotMenuData
+ ld hl, .ContestMenuHeader
+
+.GotMenuData:
+ call LoadMenuHeader
+ call .SetUpMenuItems
+ ld a, [wBattleMenuCursorBuffer]
+ ld [wMenuCursorBuffer], a
+ call .DrawMenuAccount
+ call DrawVariableLengthMenuBox
+ call .DrawBugContestStatusBox
+ call SafeUpdateSprites
+ call _OpenAndCloseMenu_HDMATransferTilemapAndAttrmap
+ farcall LoadFonts_NoOAMUpdate
+ call .DrawBugContestStatus
+ call UpdateTimePals
+ jr .Select
+
+.Reopen:
+ call UpdateSprites
+ call UpdateTimePals
+ call .SetUpMenuItems
+ ld a, [wBattleMenuCursorBuffer]
+ ld [wMenuCursorBuffer], a
+
+.Select:
+ call .GetInput
+ jr c, .Exit
+ call ._DrawMenuAccount
+ ld a, [wMenuCursorBuffer]
+ ld [wBattleMenuCursorBuffer], a
+ call PlayClickSFX
+ call PlaceHollowCursor
+ call .OpenMenu
+
+; Menu items have different return functions.
+; For example, saving exits the menu.
+ ld hl, .MenuReturns
+ ld e, a
+ ld d, 0
+ add hl, de
+ add hl, de
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ jp hl
+
+.MenuReturns:
+ dw .Reopen
+ dw .Exit
+ dw .ExitMenuCallFuncCloseText
+ dw .ExitMenuRunScriptCloseText
+ dw .ExitMenuRunScript
+ dw .ReturnEnd
+ dw .ReturnRedraw
+
+.Exit:
+ ldh a, [hOAMUpdate]
+ push af
+ ld a, 1
+ ldh [hOAMUpdate], a
+ call LoadFontsExtra
+ pop af
+ ldh [hOAMUpdate], a
+.ReturnEnd:
+ call ExitMenu
+.ReturnEnd2:
+ call CloseText
+ call UpdateTimePals
+ ret
+
+.GetInput:
+; Return carry on exit, and no-carry on selection.
+ xor a
+ ldh [hBGMapMode], a
+ call ._DrawMenuAccount
+ call SetUpMenu
+ ld a, $ff
+ ld [wMenuSelection], a
+.loop
+ call .PrintMenuAccount
+ call GetScrollingMenuJoypad
+ ld a, [wMenuJoypad]
+ cp B_BUTTON
+ jr z, .b
+ cp A_BUTTON
+ jr z, .a
+ jr .loop
+.a
+ call PlayClickSFX
+ and a
+ ret
+.b
+ scf
+ ret
+
+.ExitMenuRunScript:
+ call ExitMenu
+ ld a, HMENURETURN_SCRIPT
+ ldh [hMenuReturn], a
+ ret
+
+.ExitMenuRunScriptCloseText:
+ call ExitMenu
+ ld a, HMENURETURN_SCRIPT
+ ldh [hMenuReturn], a
+ jr .ReturnEnd2
+
+.ExitMenuCallFuncCloseText:
+ call ExitMenu
+ ld hl, wQueuedScriptAddr
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld a, [wQueuedScriptBank]
+ rst FarCall
+ jr .ReturnEnd2
+
+.ReturnRedraw:
+ call .Clear
+ jp .Reopen
+
+.Clear:
+ call ClearBGPalettes
+ call Call_ExitMenu
+ call ReloadTilesetAndPalettes
+ call .DrawMenuAccount
+ call DrawVariableLengthMenuBox
+ call .DrawBugContestStatus
+ call UpdateSprites
+ call ReloadPalettes
+ call FinishExitMenu
+ ret
+
+.MenuHeader:
+ db MENU_BACKUP_TILES ; flags
+ menu_coords 10, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1
+ dw .MenuData
+ db 1 ; default selection
+
+.ContestMenuHeader:
+ db MENU_BACKUP_TILES ; flags
+ menu_coords 10, 2, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1
+ dw .MenuData
+ db 1 ; default selection
+
+.MenuData:
+ db STATICMENU_CURSOR | STATICMENU_WRAP | STATICMENU_ENABLE_START ; flags
+ dn 0, 0 ; rows, columns
+ dw wMenuItemsList
+ dw .MenuString
+ dw .Items
+
+.Items:
+; entries correspond to STARTMENUITEM_* constants
+ dw StartMenu_Pokedex, .PokedexString, .PokedexDesc
+ dw StartMenu_Pokemon, .PartyString, .PartyDesc
+ dw StartMenu_Pack, .PackString, .PackDesc
+ dw StartMenu_Status, .StatusString, .StatusDesc
+ dw StartMenu_Save, .SaveString, .SaveDesc
+ dw StartMenu_Option, .OptionString, .OptionDesc
+ dw StartMenu_Exit, .ExitString, .ExitDesc
+ dw StartMenu_Pokegear, .PokegearString, .PokegearDesc
+ dw StartMenu_Quit, .QuitString, .QuitDesc
+
+.PokedexString: db "#DEX@"
+.PartyString: db "#MON@"
+.PackString: db "PACK@"
+.StatusString: db "<PLAYER>@"
+.SaveString: db "SAVE@"
+.OptionString: db "OPTION@"
+.ExitString: db "EXIT@"
+.PokegearString: db "<POKE>GEAR@"
+.QuitString: db "QUIT@"
+
+.PokedexDesc:
+ db "#MON"
+ next "database@"
+
+.PartyDesc:
+ db "Party <PKMN>"
+ next "status@"
+
+.PackDesc:
+ db "Contains"
+ next "items@"
+
+.PokegearDesc:
+ db "Trainer's"
+ next "key device@"
+
+.StatusDesc:
+ db "Your own"
+ next "status@"
+
+.SaveDesc:
+ db "Save your"
+ next "progress@"
+
+.OptionDesc:
+ db "Change"
+ next "settings@"
+
+.ExitDesc:
+ db "Close this"
+ next "menu@"
+
+.QuitDesc:
+ db "Quit and"
+ next "be judged.@"
+
+.OpenMenu:
+ ld a, [wMenuSelection]
+ call .GetMenuAccountTextPointer
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ jp hl
+
+.MenuString:
+ push de
+ ld a, [wMenuSelection]
+ call .GetMenuAccountTextPointer
+ inc hl
+ inc hl
+ ld a, [hli]
+ ld d, [hl]
+ ld e, a
+ pop hl
+ call PlaceString
+ ret
+
+.MenuDesc:
+ push de
+ ld a, [wMenuSelection]
+ cp $ff
+ jr z, .none
+ call .GetMenuAccountTextPointer
+rept 4
+ inc hl
+endr
+ ld a, [hli]
+ ld d, [hl]
+ ld e, a
+ pop hl
+ call PlaceString
+ ret
+.none
+ pop de
+ ret
+
+.GetMenuAccountTextPointer:
+ ld e, a
+ ld d, 0
+ ld hl, wMenuDataPointerTableAddr
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+rept 6
+ add hl, de
+endr
+ ret
+
+.SetUpMenuItems:
+ xor a
+ ld [wWhichIndexSet], a
+ call .FillMenuList
+
+ ld hl, wStatusFlags
+ bit STATUSFLAGS_POKEDEX_F, [hl]
+ jr z, .no_pokedex
+ ld a, STARTMENUITEM_POKEDEX
+ call .AppendMenuList
+.no_pokedex
+
+ ld a, [wPartyCount]
+ and a
+ jr z, .no_pokemon
+ ld a, STARTMENUITEM_POKEMON
+ call .AppendMenuList
+.no_pokemon
+
+ ld a, [wLinkMode]
+ and a
+ jr nz, .no_pack
+ ld hl, wStatusFlags2
+ bit STATUSFLAGS2_BUG_CONTEST_TIMER_F, [hl]
+ jr nz, .no_pack
+ ld a, STARTMENUITEM_PACK
+ call .AppendMenuList
+.no_pack
+
+ ld hl, wPokegearFlags
+ bit POKEGEAR_OBTAINED_F, [hl]
+ jr z, .no_pokegear
+ ld a, STARTMENUITEM_POKEGEAR
+ call .AppendMenuList
+.no_pokegear
+
+ ld a, STARTMENUITEM_STATUS
+ call .AppendMenuList
+
+ ld a, [wLinkMode]
+ and a
+ jr nz, .no_save
+ ld hl, wStatusFlags2
+ bit STATUSFLAGS2_BUG_CONTEST_TIMER_F, [hl]
+ ld a, STARTMENUITEM_QUIT
+ jr nz, .write
+ ld a, STARTMENUITEM_SAVE
+.write
+ call .AppendMenuList
+.no_save
+
+ ld a, STARTMENUITEM_OPTION
+ call .AppendMenuList
+ ld a, STARTMENUITEM_EXIT
+ call .AppendMenuList
+ ld a, c
+ ld [wMenuItemsList], a
+ ret
+
+.FillMenuList:
+ xor a
+ ld hl, wMenuItemsList
+ ld [hli], a
+ ld a, -1
+ ld bc, wMenuItemsListEnd - (wMenuItemsList + 1)
+ call ByteFill
+ ld de, wMenuItemsList + 1
+ ld c, 0
+ ret
+
+.AppendMenuList:
+ ld [de], a
+ inc de
+ inc c
+ ret
+
+.DrawMenuAccount:
+ jp ._DrawMenuAccount
+
+.PrintMenuAccount:
+ call .IsMenuAccountOn
+ ret z
+ call ._DrawMenuAccount
+ decoord 0, 14
+ jp .MenuDesc
+
+._DrawMenuAccount:
+ call .IsMenuAccountOn
+ ret z
+ hlcoord 0, 13
+ lb bc, 5, 10
+ call ClearBox
+ hlcoord 0, 13
+ ld b, 3
+ ld c, 8
+ jp TextboxPalette
+
+.IsMenuAccountOn:
+ ld a, [wOptions2]
+ and 1 << MENU_ACCOUNT
+ ret
+
+.DrawBugContestStatusBox:
+ ld hl, wStatusFlags2
+ bit STATUSFLAGS2_BUG_CONTEST_TIMER_F, [hl]
+ ret z
+ farcall StartMenu_DrawBugContestStatusBox
+ ret
+
+.DrawBugContestStatus:
+ ld hl, wStatusFlags2
+ bit STATUSFLAGS2_BUG_CONTEST_TIMER_F, [hl]
+ jr nz, .contest
+ ret
+.contest
+ farcall StartMenu_PrintBugContestStatus
+ ret
+
+StartMenu_Exit:
+; Exit the menu.
+
+ ld a, 1
+ ret
+
+StartMenu_Quit:
+; Retire from the bug catching contest.
+
+ ld hl, .StartMenuContestEndText
+ call StartMenuYesNo
+ jr c, .DontEndContest
+ ld a, BANK(BugCatchingContestReturnToGateScript)
+ ld hl, BugCatchingContestReturnToGateScript
+ call FarQueueScript
+ ld a, 4
+ ret
+
+.DontEndContest:
+ ld a, 0
+ ret
+
+.StartMenuContestEndText:
+ text_far _StartMenuContestEndText
+ text_end
+
+StartMenu_Save:
+; Save the game.
+
+ call BufferScreen
+ farcall SaveMenu
+ jr nc, .asm_12ce0
+ ld a, 0
+ ret
+.asm_12ce0
+ ld a, 1
+ ret
+
+StartMenu_Option:
+; Game options.
+
+ call FadeToMenu
+ farcall OptionsMenu
+ ld a, 6
+ ret
+
+StartMenu_Status:
+; Player status.
+
+ call FadeToMenu
+ farcall TrainerCard
+ call CloseSubmenu
+ ld a, 0
+ ret
+
+StartMenu_Pokedex:
+ ld a, [wPartyCount]
+ and a
+ jr z, .asm_12de0
+
+ call FadeToMenu
+ farcall Pokedex
+ call CloseSubmenu
+
+.asm_12de0
+ ld a, 0
+ ret
+
+StartMenu_Pokegear:
+ call FadeToMenu
+ farcall PokeGear
+ call CloseSubmenu
+ ld a, 0
+ ret
+
+StartMenu_Pack:
+ call FadeToMenu
+ farcall Pack
+ ld a, [wPackUsedItem]
+ and a
+ jr nz, .used_item
+ call CloseSubmenu
+ ld a, 0
+ ret
+
+.used_item
+ call ExitAllMenus
+ ld a, 4
+ ret
+
+StartMenu_Pokemon:
+ ld a, [wPartyCount]
+ and a
+ jr z, .return
+
+ call FadeToMenu
+
+.choosemenu
+ xor a
+ ld [wPartyMenuActionText], a ; Choose a POKéMON.
+ call ClearBGPalettes
+
+.menu
+ farcall LoadPartyMenuGFX
+ farcall InitPartyMenuWithCancel
+ farcall InitPartyMenuGFX
+
+.menunoreload
+ farcall WritePartyMenuTilemap
+ farcall PrintPartyMenuText
+ call WaitBGMap
+ call SetPalettes ; load regular palettes?
+ call DelayFrame
+ farcall PartyMenuSelect
+ jr c, .return ; if cancelled or pressed B
+
+ call PokemonActionSubmenu
+ cp 3
+ jr z, .menu
+ cp 0
+ jr z, .choosemenu
+ cp 1
+ jr z, .menunoreload
+ cp 2
+ jr z, .quit
+
+.return
+ call CloseSubmenu
+ ld a, 0
+ ret
+
+.quit
+ ld a, b
+ push af
+ call ExitAllMenus
+ pop af
+ ret
diff --git a/engine/menus/trainer_card.asm b/engine/menus/trainer_card.asm
new file mode 100644
index 00000000..7061d4bd
--- /dev/null
+++ b/engine/menus/trainer_card.asm
@@ -0,0 +1,624 @@
+; TrainerCard.Jumptable indexes
+ const_def
+ const TRAINERCARDSTATE_PAGE1_LOADGFX ; 0
+ const TRAINERCARDSTATE_PAGE1_JOYPAD ; 1
+ const TRAINERCARDSTATE_PAGE2_LOADGFX ; 2
+ const TRAINERCARDSTATE_PAGE2_JOYPAD ; 3
+ const TRAINERCARDSTATE_PAGE3_LOADGFX ; 4
+ const TRAINERCARDSTATE_PAGE3_JOYPAD ; 5
+ const TRAINERCARDSTATE_QUIT ; 6
+
+TrainerCard:
+ ld a, [wVramState]
+ push af
+ xor a
+ ld [wVramState], a
+ ld hl, wOptions
+ ld a, [hl]
+ push af
+ set NO_TEXT_SCROLL, [hl]
+ call .InitRAM
+.loop
+ call UpdateTime
+ call JoyTextDelay
+ ld a, [wJumptableIndex]
+ bit 7, a
+ jr nz, .quit
+ ldh a, [hJoyLast]
+ and B_BUTTON
+ jr nz, .quit
+ call .RunJumptable
+ call DelayFrame
+ jr .loop
+
+.quit
+ pop af
+ ld [wOptions], a
+ pop af
+ ld [wVramState], a
+ ret
+
+.InitRAM:
+ call ClearBGPalettes
+ call ClearSprites
+ call ClearTilemap
+ call DisableLCD
+
+ ld hl, ChrisCardPic
+ ld de, vTiles2
+ ld bc, 41 tiles
+ ld a, BANK(ChrisCardPic)
+ call FarCopyBytes
+
+ ld hl, CardStatusGFX
+ ld de, vTiles2 tile $29
+ ld bc, 86 tiles
+ ld a, BANK(CardStatusGFX)
+ call FarCopyBytes
+
+ call TrainerCard_PrintTopHalfOfCard
+
+ hlcoord 0, 8
+ ld d, 6
+ call TrainerCard_InitBorder
+
+ call EnableLCD
+ call WaitBGMap
+ ld b, SCGB_TRAINER_CARD
+ call GetSGBLayout
+ call SetPalettes
+ call WaitBGMap
+ ld hl, wJumptableIndex
+ xor a ; TRAINERCARDSTATE_PAGE1_LOADGFX
+ ld [hli], a ; wJumptableIndex
+ ld [hli], a ; wTrainerCardBadgeFrameCounter
+ ld [hli], a ; wTrainerCardBadgeTileID
+ ld [hl], a ; wTrainerCardBadgeAttributes
+ ret
+
+.RunJumptable:
+ jumptable .Jumptable, wJumptableIndex
+
+.Jumptable:
+; entries correspond to TRAINERCARDSTATE_* constants
+ dw TrainerCard_Page1_LoadGFX
+ dw TrainerCard_Page1_Joypad
+ dw TrainerCard_Page2_LoadGFX
+ dw TrainerCard_Page2_Joypad
+ dw TrainerCard_Page3_LoadGFX
+ dw TrainerCard_Page3_Joypad
+ dw TrainerCard_Quit
+
+TrainerCard_IncrementJumptable:
+ ld hl, wJumptableIndex
+ inc [hl]
+ ret
+
+TrainerCard_Quit:
+ ld hl, wJumptableIndex
+ set 7, [hl]
+ ret
+
+TrainerCard_Page1_LoadGFX:
+ call ClearSprites
+ hlcoord 0, 8
+ ld d, 6
+ call TrainerCard_InitBorder
+ call WaitBGMap
+ ld de, CardStatusGFX
+ ld hl, vTiles2 tile $29
+ lb bc, BANK(CardStatusGFX), 86
+ call Request2bpp
+ call TrainerCard_Page1_PrintDexCaught_GameTime
+ call TrainerCard_IncrementJumptable
+ ret
+
+TrainerCard_Page1_Joypad:
+ call TrainerCard_Page1_PrintGameTime
+ ld hl, hJoyLast
+ ld a, [hl]
+ and D_RIGHT | A_BUTTON
+ jr nz, .pressed_right_a
+ ret
+
+.pressed_right_a
+ ld a, TRAINERCARDSTATE_PAGE2_LOADGFX
+ ld [wJumptableIndex], a
+ ret
+
+.Unreferenced_KantoCheck:
+ ld a, [wKantoBadges]
+ and a
+ ret z
+ ld a, TRAINERCARDSTATE_PAGE3_LOADGFX
+ ld [wJumptableIndex], a
+ ret
+
+TrainerCard_Page2_LoadGFX:
+ call ClearSprites
+ hlcoord 0, 8
+ ld d, 6
+ call TrainerCard_InitBorder
+ call WaitBGMap
+ ld de, LeaderGFX
+ ld hl, vTiles2 tile $29
+ lb bc, BANK(LeaderGFX), 86
+ call Request2bpp
+ ld de, BadgeGFX
+ ld hl, vTiles0 tile $00
+ lb bc, BANK(BadgeGFX), 44
+ call Request2bpp
+ call TrainerCard_Page2_3_InitObjectsAndStrings
+ call TrainerCard_IncrementJumptable
+ ret
+
+TrainerCard_Page2_Joypad:
+ ld hl, TrainerCard_JohtoBadgesOAM
+ call TrainerCard_Page2_3_AnimateBadges
+ ld hl, hJoyLast
+ ld a, [hl]
+ and A_BUTTON
+ jr nz, .Quit
+ ld a, [hl]
+ and D_LEFT
+ jr nz, .d_left
+ ret
+
+.d_left
+ ld a, TRAINERCARDSTATE_PAGE1_LOADGFX
+ ld [wJumptableIndex], a
+ ret
+
+.Unreferenced_KantoCheck:
+ ld a, [wKantoBadges]
+ and a
+ ret z
+ ld a, TRAINERCARDSTATE_PAGE3_LOADGFX
+ ld [wJumptableIndex], a
+ ret
+
+.Quit:
+ ld a, TRAINERCARDSTATE_QUIT
+ ld [wJumptableIndex], a
+ ret
+
+TrainerCard_Page3_LoadGFX:
+ call ClearSprites
+ hlcoord 0, 8
+ ld d, 6
+ call TrainerCard_InitBorder
+ call WaitBGMap
+ ld de, LeaderGFX2
+ ld hl, vTiles2 tile $29
+ lb bc, BANK(LeaderGFX2), 86
+ call Request2bpp
+ ld de, BadgeGFX2
+ ld hl, vTiles0 tile $00
+ lb bc, BANK(BadgeGFX2), 44
+ call Request2bpp
+ call TrainerCard_Page2_3_InitObjectsAndStrings
+ call TrainerCard_IncrementJumptable
+ ret
+
+TrainerCard_Page3_Joypad:
+ ld hl, TrainerCard_JohtoBadgesOAM
+ call TrainerCard_Page2_3_AnimateBadges
+ ld hl, hJoyLast
+ ld a, [hl]
+ and D_LEFT
+ jr nz, .left
+ ld a, [hl]
+ and D_RIGHT
+ jr nz, .right
+ ret
+
+.left
+ ld a, TRAINERCARDSTATE_PAGE2_LOADGFX
+ ld [wJumptableIndex], a
+ ret
+
+.right
+ ld a, TRAINERCARDSTATE_PAGE1_LOADGFX
+ ld [wJumptableIndex], a
+ ret
+
+TrainerCard_PrintTopHalfOfCard:
+ hlcoord 0, 0
+ ld d, 5
+ call TrainerCard_InitBorder
+ hlcoord 2, 2
+ ld de, .Name_Money
+ call PlaceString
+ hlcoord 2, 4
+ ld de, .ID_No
+ call TrainerCardSetup_PlaceTilemapString
+ hlcoord 7, 2
+ ld de, wPlayerName
+ call PlaceString
+ hlcoord 5, 4
+ ld de, wPlayerID
+ lb bc, PRINTNUM_LEADINGZEROS | 2, 5
+ call PrintNum
+ hlcoord 7, 6
+ ld de, wMoney
+ lb bc, PRINTNUM_MONEY | 3, 6
+ call PrintNum
+ hlcoord 1, 3
+ ld de, .HorizontalDivider
+ call TrainerCardSetup_PlaceTilemapString
+ hlcoord 14, 1
+ ld de, SCREEN_WIDTH
+ xor a
+ ld b, 7
+
+.row
+ ld c, 5
+ push hl
+
+.col
+ ld [hli], a
+ inc a
+ dec c
+ jr nz, .col
+ pop hl
+ add hl, de
+ dec b
+ jr nz, .row
+ ret
+
+.Name_Money:
+ db "NAME/"
+ next ""
+ next "MONEY@"
+
+.ID_No:
+ db $27, $28, -1 ; ID NO
+
+.HorizontalDivider:
+ db $25, $25, $25, $25, $25, $25, $25, $25, $25, $25, $25, $25, $26, -1 ; ____________>
+
+TrainerCard_Page1_PrintDexCaught_GameTime:
+ hlcoord 2, 10
+ ld de, .Dex_PlayTime
+ call PlaceString
+ hlcoord 12, 15
+ ld de, .Badges
+ call PlaceString
+ ld hl, wPokedexCaught
+ ld b, wEndPokedexCaught - wPokedexCaught
+ call CountSetBits
+ ld de, wNumSetBits
+ hlcoord 15, 10
+ lb bc, 1, 3
+ call PrintNum
+ call TrainerCard_Page1_PrintGameTime
+ hlcoord 2, 8
+ ld de, .StatusTilemap
+ call TrainerCardSetup_PlaceTilemapString
+ ld a, [wStatusFlags]
+ bit STATUSFLAGS_POKEDEX_F, a
+ ret nz
+ hlcoord 1, 9
+ lb bc, 2, 17
+ call ClearBox
+ ret
+
+.Dex_PlayTime:
+ db "#DEX"
+ next "PLAY TIME@"
+
+ db "@" ; unused
+
+.Badges:
+ db "BADGES▶@"
+
+.StatusTilemap:
+ db $29, $2a, $2b, $2c, $2d, -1
+
+TrainerCard_Page2_3_InitObjectsAndStrings:
+ hlcoord 2, 8
+ ld de, .BadgesTilemap
+ call TrainerCardSetup_PlaceTilemapString
+ hlcoord 2, 10
+ ld a, $29
+ ld c, 4
+.loop
+ call TrainerCard_Page2_3_PlaceLeadersFaces
+rept 4
+ inc hl
+endr
+ dec c
+ jr nz, .loop
+ hlcoord 2, 13
+ ld a, $51
+ ld c, 4
+.loop2
+ call TrainerCard_Page2_3_PlaceLeadersFaces
+rept 4
+ inc hl
+endr
+ dec c
+ jr nz, .loop2
+ xor a
+ ld [wTrainerCardBadgeFrameCounter], a
+ ld hl, TrainerCard_JohtoBadgesOAM
+ call TrainerCard_Page2_3_OAMUpdate
+ ret
+
+.BadgesTilemap:
+ db $79, $7a, $7b, $7c, $7d, -1 ; "BADGES"
+
+TrainerCardSetup_PlaceTilemapString:
+.loop
+ ld a, [de]
+ cp -1
+ ret z
+ ld [hli], a
+ inc de
+ jr .loop
+
+TrainerCard_InitBorder:
+ ld e, SCREEN_WIDTH
+.loop1
+ ld a, $23
+ ld [hli], a
+ dec e
+ jr nz, .loop1
+
+ ld a, $23
+ ld [hli], a
+ ld e, SCREEN_HEIGHT - 1
+ ld a, " "
+.loop2
+ ld [hli], a
+ dec e
+ jr nz, .loop2
+
+ ld a, $4
+ ld [hli], a
+ ld a, $23
+ ld [hli], a
+.loop3
+ ld a, $23
+ ld [hli], a
+
+ ld e, SCREEN_HEIGHT
+ ld a, " "
+.loop4
+ ld [hli], a
+ dec e
+ jr nz, .loop4
+
+ ld a, $23
+ ld [hli], a
+ dec d
+ jr nz, .loop3
+
+ ld a, $23
+ ld [hli], a
+ ld a, $24
+ ld [hli], a
+
+ ld e, SCREEN_HEIGHT - 1
+ ld a, " "
+.loop5
+ ld [hli], a
+ dec e
+ jr nz, .loop5
+ ld a, $23
+ ld [hli], a
+ ld e, SCREEN_WIDTH
+.loop6
+ ld a, $23
+ ld [hli], a
+ dec e
+ jr nz, .loop6
+ ret
+
+TrainerCard_Page2_3_PlaceLeadersFaces:
+ push de
+ push hl
+ ld [hli], a
+ inc a
+ ld [hli], a
+ inc a
+ ld [hli], a
+ inc a
+ ld [hli], a
+ inc a
+ ld de, SCREEN_WIDTH - 3
+ add hl, de
+ ld [hli], a
+ inc a
+ ld [hli], a
+ inc a
+ ld [hli], a
+ inc a
+ ld de, SCREEN_WIDTH - 3
+ add hl, de
+ ld [hli], a
+ inc a
+ ld [hli], a
+ inc a
+ ld [hli], a
+ inc a
+ pop hl
+ pop de
+ ret
+
+TrainerCard_Page1_PrintGameTime:
+ hlcoord 11, 12
+ ld de, wGameTimeHours
+ lb bc, 2, 4
+ call PrintNum
+ inc hl
+ ld de, wGameTimeMinutes
+ lb bc, PRINTNUM_LEADINGZEROS | 1, 2
+ call PrintNum
+ ldh a, [hVBlankCounter]
+ and $1f
+ ret nz
+ hlcoord 15, 12
+ ld a, [hl]
+ xor " " ^ $2e ; alternate between space and small colon ($2e) tiles
+ ld [hl], a
+ ret
+
+TrainerCard_Page2_3_AnimateBadges:
+ ldh a, [hVBlankCounter]
+ and %111
+ ret nz
+ ld a, [wTrainerCardBadgeFrameCounter]
+ inc a
+ and %111
+ ld [wTrainerCardBadgeFrameCounter], a
+ jr TrainerCard_Page2_3_OAMUpdate
+
+TrainerCard_Page2_3_OAMUpdate:
+; copy flag array pointer
+ ld a, [hli]
+ ld e, a
+ ld a, [hli]
+; get flag array
+ ld d, a
+ ld a, [de]
+ ld c, a
+ ld de, wVirtualOAMSprite00
+ ld b, NUM_JOHTO_BADGES
+.loop
+ srl c
+ push bc
+ jr nc, .skip_badge
+ push hl
+ ld a, [hli] ; y
+ ld b, a
+ ld a, [hli] ; x
+ ld c, a
+ ld a, [hli] ; pal
+ ld [wTrainerCardBadgeAttributes], a
+ ld a, [wTrainerCardBadgeFrameCounter]
+ add l
+ ld l, a
+ ld a, 0
+ adc h
+ ld h, a
+ ld a, [hl]
+ ld [wTrainerCardBadgeTileID], a
+ call .PrepOAM
+ pop hl
+.skip_badge
+ ld bc, $b ; 3 + 2 * 4
+ add hl, bc
+ pop bc
+ dec b
+ jr nz, .loop
+ ret
+
+.PrepOAM:
+ ld a, [wTrainerCardBadgeTileID]
+ and 1 << 7
+ jr nz, .xflip
+ ld hl, .facing1
+ jr .loop2
+
+.xflip
+ ld hl, .facing2
+.loop2
+ ld a, [hli]
+ cp -1
+ ret z
+ add b
+ ld [de], a ; y
+ inc de
+
+ ld a, [hli]
+ add c
+ ld [de], a ; x
+ inc de
+
+ ld a, [wTrainerCardBadgeTileID]
+ and $ff ^ (1 << 7)
+ add [hl]
+ ld [de], a ; tile id
+ inc hl
+ inc de
+
+ ld a, [wTrainerCardBadgeAttributes]
+ add [hl]
+ ld [de], a ; attributes
+ inc hl
+ inc de
+ jr .loop2
+
+.facing1
+ dbsprite 0, 0, 0, 0, $00, 0
+ dbsprite 1, 0, 0, 0, $01, 0
+ dbsprite 0, 1, 0, 0, $02, 0
+ dbsprite 1, 1, 0, 0, $03, 0
+ db -1
+
+.facing2
+ dbsprite 0, 0, 0, 0, $01, 0 | X_FLIP
+ dbsprite 1, 0, 0, 0, $00, 0 | X_FLIP
+ dbsprite 0, 1, 0, 0, $03, 0 | X_FLIP
+ dbsprite 1, 1, 0, 0, $02, 0 | X_FLIP
+ db -1
+
+TrainerCard_JohtoBadgesOAM:
+; Template OAM data for each badge on the trainer card.
+; Format:
+ ; y, x, palette
+ ; cycle 1: face tile, in1 tile, in2 tile, in3 tile
+ ; cycle 2: face tile, in1 tile, in2 tile, in3 tile
+
+ dw wJohtoBadges
+
+ ; Zephyrbadge
+ db $68, $18, 0
+ db $00, $20, $24, $20 | (1 << 7)
+ db $00, $20, $24, $20 | (1 << 7)
+
+ ; Hivebadge
+ db $68, $38, 0
+ db $04, $20, $24, $20 | (1 << 7)
+ db $04, $20, $24, $20 | (1 << 7)
+
+ ; Plainbadge
+ db $68, $58, 0
+ db $08, $20, $24, $20 | (1 << 7)
+ db $08, $20, $24, $20 | (1 << 7)
+
+ ; Fogbadge
+ db $68, $78, 0
+ db $0c, $20, $24, $20 | (1 << 7)
+ db $0c, $20, $24, $20 | (1 << 7)
+
+ ; Mineralbadge
+ db $80, $38, 0
+ db $10, $20, $24, $20 | (1 << 7)
+ db $10, $20, $24, $20 | (1 << 7)
+
+ ; Stormbadge
+ db $80, $18, 0
+ db $14, $20, $24, $20 | (1 << 7)
+ db $14, $20, $24, $20 | (1 << 7)
+
+ ; Glacierbadge
+ db $80, $58, 0
+ db $18, $20, $24, $20 | (1 << 7)
+ db $18, $20, $24, $20 | (1 << 7)
+
+ ; Risingbadge
+ ; X-flips on alternate cycles.
+ db $80, $78, 0
+ db $1c, $20, $24, $20 | (1 << 7)
+ db $1c | (1 << 7), $20, $24, $20 | (1 << 7)
+
+ChrisCardPic: INCBIN "gfx/trainer_card/chris_card.2bpp"
+CardGFX: INCBIN "gfx/trainer_card/trainer_card.2bpp"
+CardStatusGFX: INCBIN "gfx/trainer_card/card_status.2bpp"
+
+LeaderGFX: INCBIN "gfx/trainer_card/leaders.2bpp"
+LeaderGFX2: INCBIN "gfx/trainer_card/leaders.2bpp"
+BadgeGFX: INCBIN "gfx/trainer_card/badges.2bpp"
+BadgeGFX2: INCBIN "gfx/trainer_card/badges.2bpp"