; The rst vectors are unused. SECTION "rst 00", ROM0 [$00] rst $38 SECTION "rst 08", ROM0 [$08] rst $38 SECTION "rst 10", ROM0 [$10] rst $38 SECTION "rst 18", ROM0 [$18] rst $38 SECTION "rst 20", ROM0 [$20] rst $38 SECTION "rst 28", ROM0 [$28] rst $38 SECTION "rst 30", ROM0 [$30] rst $38 SECTION "rst 38", ROM0 [$38] rst $38 ; Hardware interrupts SECTION "vblank", ROM0 [$40] jp VBlank SECTION "hblank", ROM0 [$48] rst $38 SECTION "timer", ROM0 [$50] jp Timer SECTION "serial", ROM0 [$58] jp Serial SECTION "joypad", ROM0 [$60] reti SECTION "Home", ROM0 DisableLCD:: xor a ld [rIF], a ld a, [rIE] ld b, a res 0, a ld [rIE], a .wait ld a, [rLY] cp LY_VBLANK jr nz, .wait ld a, [rLCDC] and $ff ^ rLCDC_ENABLE_MASK ld [rLCDC], a ld a, b ld [rIE], a ret EnableLCD:: ld a, [rLCDC] set rLCDC_ENABLE, a ld [rLCDC], a ret ClearSprites:: xor a ld hl, wOAMBuffer ld b, 40 * 4 .loop ld [hli], a dec b jr nz, .loop ret HideSprites:: ld a, 160 ld hl, wOAMBuffer ld de, 4 ld b, 40 .loop ld [hl], a add hl, de dec b jr nz, .loop ret INCLUDE "home/copy.asm" SECTION "Entry", ROM0 [$100] nop jp Start SECTION "Header", ROM0 [$104] ; The header is generated by rgbfix. ; The space here is allocated to prevent code from being overwritten. ds $150 - $104 SECTION "Main", ROM0 Start:: cp GBC jr z, .gbc xor a jr .ok .gbc ld a, 0 .ok ld [wGBC], a jp Init INCLUDE "home/joypad.asm" INCLUDE "data/map_header_pointers.asm" INCLUDE "home/overworld.asm" CheckForUserInterruption:: ; Return carry if Up+Select+B, Start or A are pressed in c frames. ; Used only in the intro and title screen. call DelayFrame push bc call JoypadLowSensitivity pop bc ld a, [hJoyHeld] cp D_UP + SELECT + B_BUTTON jr z, .input ld a, [hJoy5] and START | A_BUTTON jr nz, .input dec c jr nz, CheckForUserInterruption and a ret .input scf ret ; function to load position data for destination warp when switching maps ; INPUT: ; a = ID of destination warp within destination map LoadDestinationWarpPosition:: ld b,a ld a,[H_LOADEDROMBANK] push af ld a,[wPredefParentBank] ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a ld a,b add a add a ld c,a ld b,0 add hl,bc ld bc,4 ld de,wCurrentTileBlockMapViewPointer call CopyData pop af ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a ret DrawHPBar:: ; Draw an HP bar d tiles long, and fill it to e pixels. ; If c is nonzero, show at least a sliver regardless. ; The right end of the bar changes with [wHPBarType]. push hl push de push bc ; Left ld a, $71 ; "HP:" ld [hli], a ld a, $62 ld [hli], a push hl ; Middle ld a, $63 ; empty .draw ld [hli],a dec d jr nz, .draw ; Right ld a,[wHPBarType] dec a ld a, $6d ; status screen and battle jr z, .ok dec a ; pokemon menu .ok ld [hl],a pop hl ld a, e and a jr nz, .fill ; If c iz nonzero, draw a pixel anyway. ld a, c and a jr z, .done ld e, 1 .fill ld a, e sub 8 jr c, .partial ld e, a ld a, $6b ; full ld [hli], a ld a, e and a jr z, .done jr .fill .partial ; Fill remaining pixels at the end if necessary. ld a, $63 ; empty add e ld [hl], a .done pop bc pop de pop hl ret ; loads pokemon data from one of multiple sources to wLoadedMon ; loads base stats to wMonHeader ; INPUT: ; [wWhichPokemon] = index of pokemon within party/box ; [wMonDataLocation] = source ; 00: player's party ; 01: enemy's party ; 02: current box ; 03: daycare ; OUTPUT: ; [wcf91] = pokemon ID ; wLoadedMon = base address of pokemon data ; wMonHeader = base address of base stats LoadMonData:: jpab LoadMonData_ OverwritewMoves:: ; Write c to [wMoves + b]. Unused. ld hl, wMoves ld e, b ld d, 0 add hl, de ld a, c ld [hl], a ret LoadFlippedFrontSpriteByMonIndex:: ld a, 1 ld [wSpriteFlipped], a LoadFrontSpriteByMonIndex:: push hl ld a, [wd11e] push af ld a, [wcf91] ld [wd11e], a predef IndexToPokedex ld hl, wd11e ld a, [hl] pop bc ld [hl], b and a pop hl jr z, .invalidDexNumber ; dex #0 invalid cp NUM_POKEMON + 1 jr c, .validDexNumber ; dex >#151 invalid .invalidDexNumber ld a, RHYDON ; $1 ld [wcf91], a ret .validDexNumber push hl ld de, vFrontPic call LoadMonFrontSprite pop hl ld a, [H_LOADEDROMBANK] push af ld a, Bank(CopyUncompressedPicToHL) ld [H_LOADEDROMBANK], a ld [MBC1RomBank], a xor a ld [hStartTileID], a call CopyUncompressedPicToHL xor a ld [wSpriteFlipped], a pop af ld [H_LOADEDROMBANK], a ld [MBC1RomBank], a ret PlayCry:: ; Play monster a's cry. call GetCryData call PlaySound jp WaitForSoundToFinish GetCryData:: ; Load cry data for monster a. dec a ld c, a ld b, 0 ld hl, CryData add hl, bc add hl, bc add hl, bc ld a, BANK(CryData) call BankswitchHome ld a, [hli] ld b, a ; cry id ld a, [hli] ld [wFrequencyModifier], a ld a, [hl] ld [wTempoModifier], a call BankswitchBack ; Cry headers have 3 channels, ; and start from index $14, ; so add 3 times the cry id. ld a, b ld c, $14 rlca ; * 2 add b add c ret DisplayPartyMenu:: ld a,[hTilesetType] push af xor a ld [hTilesetType],a call GBPalWhiteOutWithDelay3 call ClearSprites call PartyMenuInit call DrawPartyMenu jp HandlePartyMenuInput GoBackToPartyMenu:: ld a,[hTilesetType] push af xor a ld [hTilesetType],a call PartyMenuInit call RedrawPartyMenu jp HandlePartyMenuInput PartyMenuInit:: ld a, 1 ; hardcoded bank call BankswitchHome call LoadHpBarAndStatusTilePatterns ld hl, wd730 set 6, [hl] ; turn off letter printing delay xor a ; PLAYER_PARTY_DATA ld [wMonDataLocation], a ld [wMenuWatchMovingOutOfBounds], a ld hl, wTopMenuItemY inc a ld [hli], a ; top menu item Y xor a ld [hli], a ; top menu item X ld a, [wPartyAndBillsPCSavedMenuItem] push af ld [hli], a ; current menu item ID inc hl ld a, [wPartyCount] and a ; are there more than 0 pokemon in the party? jr z, .storeMaxMenuItemID dec a ; if party is not empty, the max menu item ID is ([wPartyCount] - 1) ; otherwise, it is 0 .storeMaxMenuItemID ld [hli], a ; max menu item ID ld a, [wForcePlayerToChooseMon] and a ld a, A_BUTTON | B_BUTTON jr z, .next xor a ld [wForcePlayerToChooseMon], a inc a ; a = A_BUTTON .next ld [hli], a ; menu watched keys pop af ld [hl], a ; old menu item ID ret HandlePartyMenuInput:: ld a,1 ld [wMenuWrappingEnabled],a ld a,$40 ld [wPartyMenuAnimMonEnabled],a call HandleMenuInput_ call PlaceUnfilledArrowMenuCursor ld b,a xor a ld [wPartyMenuAnimMonEnabled],a ld a,[wCurrentMenuItem] ld [wPartyAndBillsPCSavedMenuItem],a ld hl,wd730 res 6,[hl] ; turn on letter printing delay ld a,[wMenuItemToSwap] and a jp nz,.swappingPokemon pop af ld [hTilesetType],a bit 1,b jr nz,.noPokemonChosen ld a,[wPartyCount] and a jr z,.noPokemonChosen ld a,[wCurrentMenuItem] ld [wWhichPokemon],a ld hl,wPartySpecies ld b,0 ld c,a add hl,bc ld a,[hl] ld [wcf91],a ld [wBattleMonSpecies2],a call BankswitchBack and a ret .noPokemonChosen call BankswitchBack scf ret .swappingPokemon bit 1,b ; was the B button pressed? jr z,.handleSwap ; if not, handle swapping the pokemon .cancelSwap ; if the B button was pressed callba ErasePartyMenuCursors xor a ld [wMenuItemToSwap],a ld [wPartyMenuTypeOrMessageID],a call RedrawPartyMenu jr HandlePartyMenuInput .handleSwap ld a,[wCurrentMenuItem] ld [wWhichPokemon],a callba SwitchPartyMon jr HandlePartyMenuInput DrawPartyMenu:: ld hl, DrawPartyMenu_ jr DrawPartyMenuCommon RedrawPartyMenu:: ld hl, RedrawPartyMenu_ DrawPartyMenuCommon:: ld b, BANK(RedrawPartyMenu_) jp Bankswitch ; prints a pokemon's status condition ; INPUT: ; de = address of status condition ; hl = destination address PrintStatusCondition:: push de dec de dec de ; de = address of current HP ld a,[de] ld b,a dec de ld a,[de] or b ; is the pokemon's HP zero? pop de jr nz,PrintStatusConditionNotFainted ; if the pokemon's HP is 0, print "FNT" ld a,"F" ld [hli],a ld a,"N" ld [hli],a ld [hl],"T" and a ret PrintStatusConditionNotFainted: ld a,[H_LOADEDROMBANK] push af ld a,BANK(PrintStatusAilment) ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a call PrintStatusAilment ; print status condition pop bc ld a,b ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a ret ; function to print pokemon level, leaving off the ":L" if the level is at least 100 ; INPUT: ; hl = destination address ; [wLoadedMonLevel] = level PrintLevel:: ld a,$6e ; ":L" tile ID ld [hli],a ld c,2 ; number of digits ld a,[wLoadedMonLevel] ; level cp 100 jr c,PrintLevelCommon ; if level at least 100, write over the ":L" tile dec hl inc c ; increment number of digits to 3 jr PrintLevelCommon ; prints the level without leaving off ":L" regardless of level ; INPUT: ; hl = destination address ; [wLoadedMonLevel] = level PrintLevelFull:: ld a,$6e ; ":L" tile ID ld [hli],a ld c,3 ; number of digits ld a,[wLoadedMonLevel] ; level PrintLevelCommon:: ld [wd11e],a ld de,wd11e ld b,LEFT_ALIGN | 1 ; 1 byte jp PrintNumber GetwMoves:: ; Unused. Returns the move at index a from wMoves in a ld hl,wMoves ld c,a ld b,0 add hl,bc ld a,[hl] ret ; copies the base stat data of a pokemon to wMonHeader ; INPUT: ; [wd0b5] = pokemon ID GetMonHeader:: ld a,[H_LOADEDROMBANK] push af ld a,BANK(BaseStats) ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a push bc push de push hl ld a,[wd11e] push af ld a,[wd0b5] ld [wd11e],a ld de,FossilKabutopsPic ld b,$66 ; size of Kabutops fossil and Ghost sprites cp FOSSIL_KABUTOPS ; Kabutops fossil jr z,.specialID ld de,GhostPic cp MON_GHOST ; Ghost jr z,.specialID ld de,FossilAerodactylPic ld b,$77 ; size of Aerodactyl fossil sprite cp FOSSIL_AERODACTYL ; Aerodactyl fossil jr z,.specialID cp a,MEW jr z,.mew predef IndexToPokedex ; convert pokemon ID in [wd11e] to pokedex number ld a,[wd11e] dec a ld bc, MonBaseStatsEnd - MonBaseStats ld hl,BaseStats call AddNTimes ld de,wMonHeader ld bc, MonBaseStatsEnd - MonBaseStats call CopyData jr .done .specialID ld hl,wMonHSpriteDim ld [hl],b ; write sprite dimensions inc hl ld [hl],e ; write front sprite pointer inc hl ld [hl],d jr .done .mew ld hl,MewBaseStats ld de,wMonHeader ld bc,MonBaseStatsEnd - MonBaseStats ld a,BANK(MewBaseStats) call FarCopyData .done ld a,[wd0b5] ld [wMonHIndex],a pop af ld [wd11e],a pop hl pop de pop bc pop af ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a ret ; copy party pokemon's name to wcd6d GetPartyMonName2:: ld a,[wWhichPokemon] ; index within party ld hl,wPartyMonNicks ; this is called more often GetPartyMonName:: push hl push bc call SkipFixedLengthTextEntries ; add NAME_LENGTH to hl, a times ld de,wcd6d push de ld bc,NAME_LENGTH call CopyData pop de pop bc pop hl ret ; function to print a BCD (Binary-coded decimal) number ; de = address of BCD number ; hl = destination address ; c = flags and length ; bit 7: if set, do not print leading zeroes ; if unset, print leading zeroes ; bit 6: if set, left-align the string (do not pad empty digits with spaces) ; if unset, right-align the string ; bit 5: if set, print currency symbol at the beginning of the string ; if unset, do not print the currency symbol ; bits 0-4: length of BCD number in bytes ; Note that bits 5 and 7 are modified during execution. The above reflects ; their meaning at the beginning of the functions's execution. PrintBCDNumber:: ld b,c ; save flags in b res 7,c res 6,c res 5,c ; c now holds the length bit 5,b jr z,.loop bit 7,b jr nz,.loop ld [hl],"¥" inc hl .loop ld a,[de] swap a call PrintBCDDigit ; print upper digit ld a,[de] call PrintBCDDigit ; print lower digit inc de dec c jr nz,.loop bit 7,b ; were any non-zero digits printed? jr z,.done ; if so, we are done .numberEqualsZero ; if every digit of the BCD number is zero bit 6,b ; left or right alignment? jr nz,.skipRightAlignmentAdjustment dec hl ; if the string is right-aligned, it needs to be moved back one space .skipRightAlignmentAdjustment bit 5,b jr z,.skipCurrencySymbol ld [hl],"¥" inc hl .skipCurrencySymbol ld [hl],"0" call PrintLetterDelay inc hl .done ret PrintBCDDigit:: and $f and a jr z,.zeroDigit .nonzeroDigit bit 7,b ; have any non-space characters been printed? jr z,.outputDigit ; if bit 7 is set, then no numbers have been printed yet bit 5,b ; print the currency symbol? jr z,.skipCurrencySymbol ld [hl],"¥" inc hl res 5,b .skipCurrencySymbol res 7,b ; unset 7 to indicate that a nonzero digit has been reached .outputDigit add "0" ld [hli],a jp PrintLetterDelay .zeroDigit bit 7,b ; either printing leading zeroes or already reached a nonzero digit? jr z,.outputDigit ; if so, print a zero digit bit 6,b ; left or right alignment? ret nz inc hl ; if right-aligned, "print" a space by advancing the pointer ret ; uncompresses the front or back sprite of the specified mon ; assumes the corresponding mon header is already loaded ; hl contains offset to sprite pointer ($b for front or $d for back) UncompressMonSprite:: ld bc,wMonHeader add hl,bc ld a,[hli] ld [wSpriteInputPtr],a ; fetch sprite input pointer ld a,[hl] ld [wSpriteInputPtr+1],a ; define (by index number) the bank that a pokemon's image is in ; index = Mew, bank 1 ; index = Kabutops fossil, bank $B ; index < $1F, bank 9 ; $1F ≤ index < $4A, bank $A ; $4A ≤ index < $74, bank $B ; $74 ≤ index < $99, bank $C ; $99 ≤ index, bank $D ld a,[wcf91] ; XXX name for this ram location ld b,a cp MEW ld a,BANK(MewPicFront) jr z,.GotBank ld a,b cp LEAFEON ld a,BANK(LeafeonPicFront) jr z,.GotBank ld a,b cp GLACEON ld a,BANK(GlaceonPicFront) jr z,.GotBank ld a,b cp FOSSIL_KABUTOPS ld a,BANK(FossilKabutopsPic) jr z,.GotBank ld a,b cp TANGELA + 1 ld a,BANK(TangelaPicFront) jr c,.GotBank ld a,b cp MOLTRES + 1 ld a,BANK(MoltresPicFront) jr c,.GotBank ld a,b cp BEEDRILL + 2 ld a,BANK(BeedrillPicFront) jr c,.GotBank ld a,b cp STARMIE + 1 ld a,BANK(StarmiePicFront) jr c,.GotBank ld a,BANK(VictreebelPicFront) .GotBank jp UncompressSpriteData ; de: destination location LoadMonFrontSprite:: push de ld hl, wMonHFrontSprite - wMonHeader call UncompressMonSprite ld hl, wMonHSpriteDim ld a, [hli] ld c, a pop de ; fall through ; postprocesses uncompressed sprite chunks to a 2bpp sprite and loads it into video ram ; calculates alignment parameters to place both sprite chunks in the center of the 7*7 tile sprite buffers ; de: destination location ; a,c: sprite dimensions (in tiles of 8x8 each) LoadUncompressedSpriteData:: push de and $f ld [H_SPRITEWIDTH], a ; each byte contains 8 pixels (in 1bpp), so tiles=bytes for width ld b, a ld a, $7 sub b ; 7-w inc a ; 8-w srl a ; (8-w)/2 ; horizontal center (in tiles, rounded up) ld b, a add a add a add a sub b ; 7*((8-w)/2) ; skip for horizontal center (in tiles) ld [H_SPRITEOFFSET], a ld a, c swap a and $f ld b, a add a add a add a ; 8*tiles is height in bytes ld [H_SPRITEHEIGHT], a ld a, $7 sub b ; 7-h ; skip for vertical center (in tiles, relative to current column) ld b, a ld a, [H_SPRITEOFFSET] add b ; 7*((8-w)/2) + 7-h ; combined overall offset (in tiles) add a add a add a ; 8*(7*((8-w)/2) + 7-h) ; combined overall offset (in bytes) ld [H_SPRITEOFFSET], a xor a ld [$4000], a ld hl, sSpriteBuffer0 call ZeroSpriteBuffer ; zero buffer 0 ld de, sSpriteBuffer1 ld hl, sSpriteBuffer0 call AlignSpriteDataCentered ; copy and align buffer 1 to 0 (containing the MSB of the 2bpp sprite) ld hl, sSpriteBuffer1 call ZeroSpriteBuffer ; zero buffer 1 ld de, sSpriteBuffer2 ld hl, sSpriteBuffer1 call AlignSpriteDataCentered ; copy and align buffer 2 to 1 (containing the LSB of the 2bpp sprite) pop de jp InterlaceMergeSpriteBuffers ; copies and aligns the sprite data properly inside the sprite buffer ; sprite buffers are 7*7 tiles in size, the loaded sprite is centered within this area AlignSpriteDataCentered:: ld a, [H_SPRITEOFFSET] ld b, $0 ld c, a add hl, bc ld a, [H_SPRITEWIDTH] .columnLoop push af push hl ld a, [H_SPRITEHEIGHT] ld c, a .columnInnerLoop ld a, [de] inc de ld [hli], a dec c jr nz, .columnInnerLoop pop hl ld bc, 7*8 ; 7 tiles add hl, bc ; advance one full column pop af dec a jr nz, .columnLoop ret ; fills the sprite buffer (pointed to in hl) with zeros ZeroSpriteBuffer:: ld bc, SPRITEBUFFERSIZE .nextByteLoop xor a ld [hli], a dec bc ld a, b or c jr nz, .nextByteLoop ret ; combines the (7*7 tiles, 1bpp) sprite chunks in buffer 0 and 1 into a 2bpp sprite located in buffer 1 through 2 ; in the resulting sprite, the rows of the two source sprites are interlaced ; de: output address InterlaceMergeSpriteBuffers:: xor a ld [$4000], a push de ld hl, sSpriteBuffer2 + (SPRITEBUFFERSIZE - 1) ; destination: end of buffer 2 ld de, sSpriteBuffer1 + (SPRITEBUFFERSIZE - 1) ; source 2: end of buffer 1 ld bc, sSpriteBuffer0 + (SPRITEBUFFERSIZE - 1) ; source 1: end of buffer 0 ld a, SPRITEBUFFERSIZE/2 ; $c4 ld [H_SPRITEINTERLACECOUNTER], a .interlaceLoop ld a, [de] dec de ld [hld], a ; write byte of source 2 ld a, [bc] dec bc ld [hld], a ; write byte of source 1 ld a, [de] dec de ld [hld], a ; write byte of source 2 ld a, [bc] dec bc ld [hld], a ; write byte of source 1 ld a, [H_SPRITEINTERLACECOUNTER] dec a ld [H_SPRITEINTERLACECOUNTER], a jr nz, .interlaceLoop ld a, [wSpriteFlipped] and a jr z, .notFlipped ld bc, 2*SPRITEBUFFERSIZE ld hl, sSpriteBuffer1 .swapLoop swap [hl] ; if flipped swap nybbles in all bytes inc hl dec bc ld a, b or c jr nz, .swapLoop .notFlipped pop hl ld de, sSpriteBuffer1 ld c, (2*SPRITEBUFFERSIZE)/16 ; $31, number of 16 byte chunks to be copied ld a, [H_LOADEDROMBANK] ld b, a jp CopyVideoData INCLUDE "data/collision.asm" INCLUDE "home/copy2.asm" INCLUDE "home/text.asm" INCLUDE "home/vcopy.asm" INCLUDE "home/init.asm" INCLUDE "home/vblank.asm" INCLUDE "home/fade.asm" INCLUDE "home/serial.asm" INCLUDE "home/timer.asm" INCLUDE "home/audio.asm" UpdateSprites:: ld a, [wUpdateSpritesEnabled] dec a ret nz ld a, [H_LOADEDROMBANK] push af ld a, Bank(_UpdateSprites) ld [H_LOADEDROMBANK], a ld [MBC1RomBank], a call _UpdateSprites pop af ld [H_LOADEDROMBANK], a ld [MBC1RomBank], a ret INCLUDE "data/mart_inventories.asm" TextScriptEndingChar:: db "@" TextScriptEnd:: ld hl,TextScriptEndingChar ret ExclamationText:: TX_FAR _ExclamationText db "@" GroundRoseText:: TX_FAR _GroundRoseText db "@" BoulderText:: TX_FAR _BoulderText db "@" MartSignText:: TX_FAR _MartSignText db "@" PokeCenterSignText:: TX_FAR _PokeCenterSignText db "@" PickUpItemText:: TX_ASM predef PickUpItem jp TextScriptEnd INCLUDE "home/pic.asm" ResetPlayerSpriteData:: ld hl, wSpriteStateData1 call ResetPlayerSpriteData_ClearSpriteData ld hl, wSpriteStateData2 call ResetPlayerSpriteData_ClearSpriteData ld a, $1 ld [wSpriteStateData1], a ld [wSpriteStateData2 + $0e], a ld hl, wSpriteStateData1 + 4 ld [hl], $3c ; set Y screen pos inc hl inc hl ld [hl], $40 ; set X screen pos ret ; overwrites sprite data with zeroes ResetPlayerSpriteData_ClearSpriteData:: ld bc, $10 xor a jp FillMemory FadeOutAudio:: ld a, [wAudioFadeOutControl] and a ; currently fading out audio? jr nz, .fadingOut ld a, [wd72c] bit 1, a ret nz ld a, $77 ld [rNR50], a ret .fadingOut ld a, [wAudioFadeOutCounter] and a jr z, .counterReachedZero dec a ld [wAudioFadeOutCounter], a ret .counterReachedZero ld a, [wAudioFadeOutCounterReloadValue] ld [wAudioFadeOutCounter], a ld a, [rNR50] and a ; has the volume reached 0? jr z, .fadeOutComplete ld b, a and $f dec a ld c, a ld a, b and $f0 swap a dec a swap a or c ld [rNR50], a ret .fadeOutComplete ld a, [wAudioFadeOutControl] ld b, a xor a ld [wAudioFadeOutControl], a ld a, $ff ld [wNewSoundID], a call PlaySound ld a, [wAudioSavedROMBank] ld [wAudioROMBank], a ld a, b ld [wNewSoundID], a jp PlaySound ; this function is used to display sign messages, sprite dialog, etc. ; INPUT: [hSpriteIndexOrTextID] = sprite ID or text ID DisplayTextID:: ld a,[H_LOADEDROMBANK] push af callba DisplayTextIDInit ; initialization ld hl,wTextPredefFlag bit 0,[hl] res 0,[hl] jr nz,.skipSwitchToMapBank ld a,[wCurMap] call SwitchToMapRomBank .skipSwitchToMapBank ld a,30 ; half a second ld [H_FRAMECOUNTER],a ; used as joypad poll timer ld hl,wMapTextPtr ld a,[hli] ld h,[hl] ld l,a ; hl = map text pointer ld d,$00 ld a,[hSpriteIndexOrTextID] ; text ID ld [wSpriteIndex],a and a jp z,DisplayStartMenu cp TEXT_SAFARI_GAME_OVER jp z,DisplaySafariGameOverText cp TEXT_MON_FAINTED jp z,DisplayPokemonFaintedText cp TEXT_BLACKED_OUT jp z,DisplayPlayerBlackedOutText cp TEXT_REPEL_WORE_OFF jp z,DisplayRepelWoreOffText ld a,[wNumSprites] ld e,a ld a,[hSpriteIndexOrTextID] ; sprite ID cp e jr z,.spriteHandling jr nc,.skipSpriteHandling .spriteHandling ; get the text ID of the sprite push hl push de push bc callba UpdateSpriteFacingOffsetAndDelayMovement ; update the graphics of the sprite the player is talking to (to face the right direction) pop bc pop de ld hl,wMapSpriteData ; NPC text entries ld a,[hSpriteIndexOrTextID] dec a add a add l ld l,a jr nc,.noCarry inc h .noCarry inc hl ld a,[hl] ; a = text ID of the sprite pop hl .skipSpriteHandling ; look up the address of the text in the map's text entries dec a ld e,a sla e add hl,de ld a,[hli] ld h,[hl] ld l,a ; hl = address of the text ld a,[hl] ; a = first byte of text ; check first byte of text for special cases cp $fe ; Pokemart NPC jp z,DisplayPokemartDialogue cp $ff ; Pokemon Center NPC jp z,DisplayPokemonCenterDialogue cp $fc ; Item Storage PC jp z,FuncTX_ItemStoragePC cp $fd ; Bill's PC jp z,FuncTX_BillsPC cp $f9 ; Pokemon Center PC jp z,FuncTX_PokemonCenterPC cp $f5 ; Vending Machine jr nz,.notVendingMachine callba VendingMachineMenu ; jump banks to vending machine routine jr AfterDisplayingTextID .notVendingMachine cp $f7 ; prize menu jp z, FuncTX_GameCornerPrizeMenu cp $f6 ; cable connection NPC in Pokemon Center jr nz,.notSpecialCase callab CableClubNPC jr AfterDisplayingTextID .notSpecialCase call PrintText_NoCreatingTextBox ; display the text ld a,[wDoNotWaitForButtonPressAfterDisplayingText] and a jr nz,HoldTextDisplayOpen AfterDisplayingTextID:: ld a,[wEnteringCableClub] and a jr nz,HoldTextDisplayOpen call WaitForTextScrollButtonPress ; wait for a button press after displaying all the text ; loop to hold the dialogue box open as long as the player keeps holding down the A button HoldTextDisplayOpen:: call Joypad ld a,[hJoyHeld] bit 0,a ; is the A button being pressed? jr nz,HoldTextDisplayOpen CloseTextDisplay:: ld a,[wCurMap] call SwitchToMapRomBank ld a,$90 ld [hWY],a ; move the window off the screen call DelayFrame call LoadGBPal xor a ld [H_AUTOBGTRANSFERENABLED],a ; disable continuous WRAM to VRAM transfer each V-blank ; loop to make sprites face the directions they originally faced before the dialogue ld hl,wSpriteStateData2 + $19 ld c,$0f ld de,$0010 .restoreSpriteFacingDirectionLoop ld a,[hl] dec h ld [hl],a inc h add hl,de dec c jr nz,.restoreSpriteFacingDirectionLoop ld a,BANK(InitMapSprites) ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a call InitMapSprites ; reload sprite tile pattern data (since it was partially overwritten by text tile patterns) ld hl,wFontLoaded res 0,[hl] ld a,[wd732] bit 3,a ; used fly warp call z,LoadPlayerSpriteGraphics call LoadCurrentMapView pop af ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a jp UpdateSprites DisplayPokemartDialogue:: push hl ld hl,PokemartGreetingText call PrintText pop hl inc hl call LoadItemList ld a,PRICEDITEMLISTMENU ld [wListMenuID],a ld a,[H_LOADEDROMBANK] push af ld a,Bank(DisplayPokemartDialogue_) ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a call DisplayPokemartDialogue_ pop af ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a jp AfterDisplayingTextID PokemartGreetingText:: TX_FAR _PokemartGreetingText db "@" LoadItemList:: ld a,1 ld [wUpdateSpritesEnabled],a ld a,h ld [wItemListPointer],a ld a,l ld [wItemListPointer + 1],a ld de,wItemList .loop ld a,[hli] ld [de],a inc de cp $ff jr nz,.loop ret DisplayPokemonCenterDialogue:: ; zeroing these doesn't appear to serve any purpose xor a ld [$ff8b],a ld [$ff8c],a ld [$ff8d],a inc hl ld a,[H_LOADEDROMBANK] push af ld a,Bank(DisplayPokemonCenterDialogue_) ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a call DisplayPokemonCenterDialogue_ pop af ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a jp AfterDisplayingTextID DisplaySafariGameOverText:: callab PrintSafariGameOverText jp AfterDisplayingTextID DisplayPokemonFaintedText:: ld hl,PokemonFaintedText call PrintText jp AfterDisplayingTextID PokemonFaintedText:: TX_FAR _PokemonFaintedText db "@" DisplayPlayerBlackedOutText:: ld hl,PlayerBlackedOutText call PrintText ld a,[wd732] res 5,a ; reset forced to use bike bit ld [wd732],a jp HoldTextDisplayOpen PlayerBlackedOutText:: TX_FAR _PlayerBlackedOutText db "@" DisplayRepelWoreOffText:: ld hl,RepelWoreOffText call PrintText jp AfterDisplayingTextID RepelWoreOffText:: TX_FAR _RepelWoreOffText db "@" INCLUDE "engine/menu/start_menu.asm" ; function to count how many bits are set in a string of bytes ; INPUT: ; hl = address of string of bytes ; b = length of string of bytes ; OUTPUT: ; [wNumSetBits] = number of set bits CountSetBits:: ld c,0 .loop ld a,[hli] ld e,a ld d,8 .innerLoop ; count how many bits are set in the current byte srl e ld a,0 adc c ld c,a dec d jr nz,.innerLoop dec b jr nz,.loop ld a,c ld [wNumSetBits],a ret ; subtracts the amount the player paid from their money ; sets carry flag if there is enough money and unsets carry flag if not SubtractAmountPaidFromMoney:: jpba SubtractAmountPaidFromMoney_ ; adds the amount the player sold to their money AddAmountSoldToMoney:: ld de,wPlayerMoney + 2 ld hl,$ffa1 ; total price of items ld c,3 ; length of money in bytes predef AddBCDPredef ; add total price to money ld a,MONEY_BOX ld [wTextBoxID],a call DisplayTextBoxID ; redraw money text box ld a, SFX_PURCHASE call PlaySoundWaitForCurrent jp WaitForSoundToFinish ; function to remove an item (in varying quantities) from the player's bag or PC box ; INPUT: ; HL = address of inventory (either wNumBagItems or wNumBoxItems) ; [wWhichPokemon] = index (within the inventory) of the item to remove ; [wItemQuantity] = quantity to remove RemoveItemFromInventory:: ld a,[H_LOADEDROMBANK] push af ld a,BANK(RemoveItemFromInventory_) ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a call RemoveItemFromInventory_ pop af ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a ret ; function to add an item (in varying quantities) to the player's bag or PC box ; INPUT: ; HL = address of inventory (either wNumBagItems or wNumBoxItems) ; [wcf91] = item ID ; [wItemQuantity] = item quantity ; sets carry flag if successful, unsets carry flag if unsuccessful AddItemToInventory:: push bc ld a,[H_LOADEDROMBANK] push af ld a,BANK(AddItemToInventory_) ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a call AddItemToInventory_ pop bc ld a,b ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a pop bc ret ; INPUT: ; [wListMenuID] = list menu ID ; [wListPointer] = address of the list (2 bytes) DisplayListMenuID:: xor a ld [H_AUTOBGTRANSFERENABLED],a ; disable auto-transfer ld a,1 ld [hJoy7],a ; joypad state update flag ld a,[wBattleType] and a ; is it the Old Man battle? jr nz,.specialBattleType ld a,$01 ; hardcoded bank jr .bankswitch .specialBattleType ; Old Man battle ld a, BANK(DisplayBattleMenu) .bankswitch call BankswitchHome ld hl,wd730 set 6,[hl] ; turn off letter printing delay xor a ld [wMenuItemToSwap],a ; 0 means no item is currently being swapped ld [wListCount],a ld a,[wListPointer] ld l,a ld a,[wListPointer + 1] ld h,a ; hl = address of the list ld a,[hl] ; the first byte is the number of entries in the list ld [wListCount],a ld a,LIST_MENU_BOX ld [wTextBoxID],a call DisplayTextBoxID ; draw the menu text box call UpdateSprites ; disable sprites behind the text box ; the code up to .skipMovingSprites appears to be useless coord hl, 4, 2 ; coordinates of upper left corner of menu text box lb de, 9, 14 ; height and width of menu text box ld a,[wListMenuID] and a ; is it a PC pokemon list? jr nz,.skipMovingSprites call UpdateSprites .skipMovingSprites ld a,1 ; max menu item ID is 1 if the list has less than 2 entries ld [wMenuWatchMovingOutOfBounds],a ld a,[wListCount] cp 2 ; does the list have less than 2 entries? jr c,.setMenuVariables ld a,2 ; max menu item ID is 2 if the list has at least 2 entries .setMenuVariables ld [wMaxMenuItem],a ld a,4 ld [wTopMenuItemY],a ld a,5 ld [wTopMenuItemX],a ld a,A_BUTTON | B_BUTTON | SELECT ld [wMenuWatchedKeys],a ld c,10 call DelayFrames DisplayListMenuIDLoop:: xor a ld [H_AUTOBGTRANSFERENABLED],a ; disable transfer call PrintListMenuEntries ld a,1 ld [H_AUTOBGTRANSFERENABLED],a ; enable transfer call Delay3 ld a,[wBattleType] and a ; is it the Old Man battle? jr z,.notOldManBattle .oldManBattle ld a,"▶" Coorda 5, 4 ; place menu cursor in front of first menu entry ld c,80 call DelayFrames xor a ld [wCurrentMenuItem],a coord hl, 5, 4 ld a,l ld [wMenuCursorLocation],a ld a,h ld [wMenuCursorLocation + 1],a jr .buttonAPressed .notOldManBattle call LoadGBPal call HandleMenuInput push af call PlaceMenuCursor pop af bit 0,a ; was the A button pressed? jp z,.checkOtherKeys .buttonAPressed ld a,[wCurrentMenuItem] call PlaceUnfilledArrowMenuCursor ; pointless because both values are overwritten before they are read ld a,$01 ld [wMenuExitMethod],a ld [wChosenMenuItem],a xor a ld [wMenuWatchMovingOutOfBounds],a ld a,[wCurrentMenuItem] ld c,a ld a,[wListScrollOffset] add c ld c,a ld a,[wListCount] and a ; is the list empty? jp z,ExitListMenu ; if so, exit the menu dec a cp c ; did the player select Cancel? jp c,ExitListMenu ; if so, exit the menu ld a,c ld [wWhichPokemon],a ld a,[wListMenuID] cp ITEMLISTMENU jr nz,.skipMultiplying ; if it's an item menu sla c ; item entries are 2 bytes long, so multiply by 2 .skipMultiplying ld a,[wListPointer] ld l,a ld a,[wListPointer + 1] ld h,a inc hl ; hl = beginning of list entries ld b,0 add hl,bc ld a,[hl] ld [wcf91],a ld a,[wListMenuID] and a ; is it a PC pokemon list? jr z,.pokemonList push hl call GetItemPrice pop hl ld a,[wListMenuID] cp ITEMLISTMENU jr nz,.skipGettingQuantity ; if it's an item menu inc hl ld a,[hl] ; a = item quantity ld [wMaxItemQuantity],a .skipGettingQuantity ld a,[wcf91] ld [wd0b5],a ld a,BANK(ItemNames) ld [wPredefBank],a call GetName jr .storeChosenEntry .pokemonList ld hl,wPartyCount ld a,[wListPointer] cp l ; is it a list of party pokemon or box pokemon? ld hl,wPartyMonNicks jr z,.getPokemonName ld hl, wBoxMonNicks ; box pokemon names .getPokemonName ld a,[wWhichPokemon] call GetPartyMonName .storeChosenEntry ; store the menu entry that the player chose and return ld de,wcd6d call CopyStringToCF4B ; copy name to wcf4b ld a,CHOSE_MENU_ITEM ld [wMenuExitMethod],a ld a,[wCurrentMenuItem] ld [wChosenMenuItem],a xor a ld [hJoy7],a ; joypad state update flag ld hl,wd730 res 6,[hl] ; turn on letter printing delay jp BankswitchBack .checkOtherKeys ; check B, SELECT, Up, and Down keys bit 1,a ; was the B button pressed? jp nz,ExitListMenu ; if so, exit the menu bit 2,a ; was the select button pressed? jp nz,HandleItemListSwapping ; if so, allow the player to swap menu entries ld b,a bit 7,b ; was Down pressed? ld hl,wListScrollOffset jr z,.upPressed .downPressed ld a,[hl] add 3 ld b,a ld a,[wListCount] cp b ; will going down scroll past the Cancel button? jp c,DisplayListMenuIDLoop inc [hl] ; if not, go down jp DisplayListMenuIDLoop .upPressed ld a,[hl] and a jp z,DisplayListMenuIDLoop dec [hl] jp DisplayListMenuIDLoop DisplayChooseQuantityMenu:: ; text box dimensions/coordinates for just quantity coord hl, 15, 9 ld b,1 ; height ld c,3 ; width ld a,[wListMenuID] cp PRICEDITEMLISTMENU jr nz,.drawTextBox ; text box dimensions/coordinates for quantity and price coord hl, 7, 9 ld b,1 ; height ld c,11 ; width .drawTextBox call TextBoxBorder coord hl, 16, 10 ld a,[wListMenuID] cp PRICEDITEMLISTMENU jr nz,.printInitialQuantity coord hl, 8, 10 .printInitialQuantity ld de,InitialQuantityText call PlaceString xor a ld [wItemQuantity],a ; initialize current quantity to 0 jp .incrementQuantity .waitForKeyPressLoop call JoypadLowSensitivity ld a,[hJoyPressed] ; newly pressed buttons bit 0,a ; was the A button pressed? jp nz,.buttonAPressed bit 1,a ; was the B button pressed? jp nz,.buttonBPressed bit 6,a ; was Up pressed? jr nz,.incrementQuantity bit 7,a ; was Down pressed? jr nz,.decrementQuantity jr .waitForKeyPressLoop .incrementQuantity ld a,[wMaxItemQuantity] inc a ld b,a ld hl,wItemQuantity ; current quantity inc [hl] ld a,[hl] cp b jr nz,.handleNewQuantity ; wrap to 1 if the player goes above the max quantity ld a,1 ld [hl],a jr .handleNewQuantity .decrementQuantity ld hl,wItemQuantity ; current quantity dec [hl] jr nz,.handleNewQuantity ; wrap to the max quantity if the player goes below 1 ld a,[wMaxItemQuantity] ld [hl],a .handleNewQuantity coord hl, 17, 10 ld a,[wListMenuID] cp PRICEDITEMLISTMENU jr nz,.printQuantity .printPrice ld c,$03 ld a,[wItemQuantity] ld b,a ld hl,hMoney ; total price ; initialize total price to 0 xor a ld [hli],a ld [hli],a ld [hl],a .addLoop ; loop to multiply the individual price by the quantity to get the total price ld de,hMoney + 2 ld hl,hItemPrice + 2 push bc predef AddBCDPredef ; add the individual price to the current sum pop bc dec b jr nz,.addLoop ld a,[hHalveItemPrices] and a ; should the price be halved (for selling items)? jr z,.skipHalvingPrice xor a ld [hDivideBCDDivisor],a ld [hDivideBCDDivisor + 1],a ld a,$02 ld [hDivideBCDDivisor + 2],a predef DivideBCDPredef3 ; halves the price ; store the halved price ld a,[hDivideBCDQuotient] ld [hMoney],a ld a,[hDivideBCDQuotient + 1] ld [hMoney + 1],a ld a,[hDivideBCDQuotient + 2] ld [hMoney + 2],a .skipHalvingPrice coord hl, 12, 10 ld de,SpacesBetweenQuantityAndPriceText call PlaceString ld de,hMoney ; total price ld c,$a3 call PrintBCDNumber coord hl, 9, 10 .printQuantity ld de,wItemQuantity ; current quantity lb bc, LEADING_ZEROES | 1, 2 ; 1 byte, 2 digits call PrintNumber jp .waitForKeyPressLoop .buttonAPressed ; the player chose to make the transaction xor a ld [wMenuItemToSwap],a ; 0 means no item is currently being swapped ret .buttonBPressed ; the player chose to cancel the transaction xor a ld [wMenuItemToSwap],a ; 0 means no item is currently being swapped ld a,$ff ret InitialQuantityText:: db "×01@" SpacesBetweenQuantityAndPriceText:: db " @" ExitListMenu:: ld a,[wCurrentMenuItem] ld [wChosenMenuItem],a ld a,CANCELLED_MENU ld [wMenuExitMethod],a ld [wMenuWatchMovingOutOfBounds],a xor a ld [hJoy7],a ld hl,wd730 res 6,[hl] call BankswitchBack xor a ld [wMenuItemToSwap],a ; 0 means no item is currently being swapped scf ret PrintListMenuEntries:: coord hl, 5, 3 ld b,9 ld c,14 call ClearScreenArea ld a,[wListPointer] ld e,a ld a,[wListPointer + 1] ld d,a inc de ; de = beginning of list entries ld a,[wListScrollOffset] ld c,a ld a,[wListMenuID] cp ITEMLISTMENU ld a,c jr nz,.skipMultiplying ; if it's an item menu ; item entries are 2 bytes long, so multiply by 2 sla a sla c .skipMultiplying add e ld e,a jr nc,.noCarry inc d .noCarry coord hl, 6, 4 ; coordinates of first list entry name ld b,4 ; print 4 names .loop ld a,b ld [wWhichPokemon],a ld a,[de] ld [wd11e],a cp $ff jp z,.printCancelMenuItem push bc push de push hl push hl push de ld a,[wListMenuID] and a jr z,.pokemonPCMenu cp MOVESLISTMENU jr z,.movesMenu .itemMenu call GetItemName jr .placeNameString .pokemonPCMenu push hl ld hl,wPartyCount ld a,[wListPointer] cp l ; is it a list of party pokemon or box pokemon? ld hl,wPartyMonNicks jr z,.getPokemonName ld hl, wBoxMonNicks ; box pokemon names .getPokemonName ld a,[wWhichPokemon] ld b,a ld a,4 sub b ld b,a ld a,[wListScrollOffset] add b call GetPartyMonName pop hl jr .placeNameString .movesMenu call GetMoveName .placeNameString call PlaceString pop de pop hl ld a,[wPrintItemPrices] and a ; should prices be printed? jr z,.skipPrintingItemPrice .printItemPrice push hl ld a,[de] ld de,ItemPrices ld [wcf91],a call GetItemPrice ; get price pop hl ld bc, SCREEN_WIDTH + 5 ; 1 row down and 5 columns right add hl,bc ld c,$a3 ; no leading zeroes, right-aligned, print currency symbol, 3 bytes call PrintBCDNumber .skipPrintingItemPrice ld a,[wListMenuID] and a jr nz,.skipPrintingPokemonLevel .printPokemonLevel ld a,[wd11e] push af push hl ld hl,wPartyCount ld a,[wListPointer] cp l ; is it a list of party pokemon or box pokemon? ld a,PLAYER_PARTY_DATA jr z,.next ld a,BOX_DATA .next ld [wMonDataLocation],a ld hl,wWhichPokemon ld a,[hl] ld b,a ld a,$04 sub b ld b,a ld a,[wListScrollOffset] add b ld [hl],a call LoadMonData ld a,[wMonDataLocation] and a ; is it a list of party pokemon or box pokemon? jr z,.skipCopyingLevel .copyLevel ld a,[wLoadedMonBoxLevel] ld [wLoadedMonLevel],a .skipCopyingLevel pop hl ld bc,$001c add hl,bc call PrintLevel pop af ld [wd11e],a .skipPrintingPokemonLevel pop hl pop de inc de ld a,[wListMenuID] cp ITEMLISTMENU jr nz,.nextListEntry .printItemQuantity ld a,[wd11e] ld [wcf91],a call IsKeyItem ; check if item is unsellable ld a,[wIsKeyItem] and a ; is the item unsellable? jr nz,.skipPrintingItemQuantity ; if so, don't print the quantity push hl ld bc, SCREEN_WIDTH + 8 ; 1 row down and 8 columns right add hl,bc ld a,"×" ld [hli],a ld a,[wd11e] push af ld a,[de] ld [wMaxItemQuantity],a push de ld de,wd11e ld [de],a lb bc, 1, 2 call PrintNumber pop de pop af ld [wd11e],a pop hl .skipPrintingItemQuantity inc de pop bc inc c push bc inc c ld a,[wMenuItemToSwap] ; ID of item chosen for swapping (counts from 1) and a ; is an item being swapped? jr z,.nextListEntry sla a cp c ; is it this item? jr nz,.nextListEntry dec hl ld a,$ec ; unfilled right arrow menu cursor to indicate an item being swapped ld [hli],a .nextListEntry ld bc,2 * SCREEN_WIDTH ; 2 rows add hl,bc pop bc inc c dec b jp nz,.loop ld bc,-8 add hl,bc ld a,"▼" ld [hl],a ret .printCancelMenuItem ld de,ListMenuCancelText jp PlaceString ListMenuCancelText:: db "CANCEL@" GetMonName:: push hl ld a,[H_LOADEDROMBANK] push af ld a,BANK(MonsterNames) ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a ld a,[wd11e] dec a ld hl,MonsterNames ld c,10 ld b,0 call AddNTimes ld de,wcd6d push de ld bc,10 call CopyData ld hl,wcd6d + 10 ld [hl], "@" pop de pop af ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a pop hl ret GetItemName:: ; given an item ID at [wd11e], store the name of the item into a string ; starting at wcd6d push hl push bc ld a,[wd11e] cp HM_01 ; is this a TM/HM? jr nc,.Machine ld [wd0b5],a ld a,ITEM_NAME ld [wNameListType],a ld a,BANK(ItemNames) ld [wPredefBank],a call GetName jr .Finish .Machine call GetMachineName .Finish ld de,wcd6d ; pointer to where item name is stored in RAM pop bc pop hl ret GetMachineName:: ; copies the name of the TM/HM in [wd11e] to wcd6d push hl push de push bc ld a,[wd11e] push af cp TM_01 ; is this a TM? [not HM] jr nc,.WriteTM ; if HM, then write "HM" and add 5 to the item ID, so we can reuse the ; TM printing code add 5 ld [wd11e],a ld hl,HiddenPrefix ; points to "HM" ld bc,2 jr .WriteMachinePrefix .WriteTM ld hl,TechnicalPrefix ; points to "TM" ld bc,2 .WriteMachinePrefix ld de,wcd6d call CopyData ; now get the machine number and convert it to text ld a,[wd11e] sub TM_01 - 1 ld b, "0" .FirstDigit sub 10 jr c,.SecondDigit inc b jr .FirstDigit .SecondDigit add 10 push af ld a,b ld [de],a inc de pop af ld b, "0" add b ld [de],a inc de ld a,"@" ld [de],a pop af ld [wd11e],a pop bc pop de pop hl ret TechnicalPrefix:: db "TM" HiddenPrefix:: db "HM" ; sets carry if item is HM, clears carry if item is not HM ; Input: a = item ID IsItemHM:: cp HM_01 jr c,.notHM cp TM_01 ret .notHM and a ret ; sets carry if move is an HM, clears carry if move is not an HM ; Input: a = move ID IsMoveHM:: ld hl,HMMoves ld de,1 jp IsInArray HMMoves:: db CUT,FLY,SURF,STRENGTH,FLASH db $ff ; terminator GetMoveName:: push hl ld a,MOVE_NAME ld [wNameListType],a ld a,[wd11e] ld [wd0b5],a ld a,BANK(MoveNames) ld [wPredefBank],a call GetName ld de,wcd6d ; pointer to where move name is stored in RAM pop hl ret ; reloads text box tile patterns, current map view, and tileset tile patterns ReloadMapData:: ld a,[H_LOADEDROMBANK] push af ld a,[wCurMap] call SwitchToMapRomBank call DisableLCD call LoadTextBoxTilePatterns call LoadCurrentMapView call LoadTilesetTilePatternData call EnableLCD pop af ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a ret ; reloads tileset tile patterns ReloadTilesetTilePatterns:: ld a,[H_LOADEDROMBANK] push af ld a,[wCurMap] call SwitchToMapRomBank call DisableLCD call LoadTilesetTilePatternData call EnableLCD pop af ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a ret ; shows the town map and lets the player choose a destination to fly to ChooseFlyDestination:: ld hl,wd72e res 4,[hl] jpba LoadTownMap_Fly ; causes the text box to close without waiting for a button press after displaying text DisableWaitingAfterTextDisplay:: ld a,$01 ld [wDoNotWaitForButtonPressAfterDisplayingText],a ret ; uses an item ; UseItem is used with dummy items to perform certain other functions as well ; INPUT: ; [wcf91] = item ID ; OUTPUT: ; [wActionResultOrTookBattleTurn] = success ; 00: unsucessful ; 01: successful ; 02: not able to be used right now, no extra menu displayed (only certain items use this) UseItem:: jpba UseItem_ ; confirms the item toss and then tosses the item ; INPUT: ; hl = address of inventory (either wNumBagItems or wNumBoxItems) ; [wcf91] = item ID ; [wWhichPokemon] = index of item within inventory ; [wItemQuantity] = quantity to toss ; OUTPUT: ; clears carry flag if the item is tossed, sets carry flag if not TossItem:: ld a,[H_LOADEDROMBANK] push af ld a,BANK(TossItem_) ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a call TossItem_ pop de ld a,d ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a ret ; checks if an item is a key item ; INPUT: ; [wcf91] = item ID ; OUTPUT: ; [wIsKeyItem] = result ; 00: item is not key item ; 01: item is key item IsKeyItem:: push hl push de push bc callba IsKeyItem_ pop bc pop de pop hl ret ; function to draw various text boxes ; INPUT: ; [wTextBoxID] = text box ID ; b, c = y, x cursor position (TWO_OPTION_MENU only) DisplayTextBoxID:: ld a,[H_LOADEDROMBANK] push af ld a,BANK(DisplayTextBoxID_) ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a call DisplayTextBoxID_ pop bc ld a,b ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a ret ; not zero if an NPC movement script is running, the player character is ; automatically stepping down from a door, or joypad states are being simulated IsPlayerCharacterBeingControlledByGame:: ld a, [wNPCMovementScriptPointerTableNum] and a ret nz ld a, [wd736] bit 1, a ; currently stepping down from door bit ret nz ld a, [wd730] and $80 ret RunNPCMovementScript:: ld hl, wd736 bit 0, [hl] res 0, [hl] jr nz, .playerStepOutFromDoor ld a, [wNPCMovementScriptPointerTableNum] and a ret z dec a add a ld d, 0 ld e, a ld hl, .NPCMovementScriptPointerTables add hl, de ld a, [hli] ld h, [hl] ld l, a ld a, [H_LOADEDROMBANK] push af ld a, [wNPCMovementScriptBank] ld [H_LOADEDROMBANK], a ld [MBC1RomBank], a ld a, [wNPCMovementScriptFunctionNum] call CallFunctionInTable pop af ld [H_LOADEDROMBANK], a ld [MBC1RomBank], a ret .NPCMovementScriptPointerTables dw PalletMovementScriptPointerTable dw PewterMuseumGuyMovementScriptPointerTable dw PewterGymGuyMovementScriptPointerTable .playerStepOutFromDoor jpba PlayerStepOutFromDoor EndNPCMovementScript:: jpba _EndNPCMovementScript EmptyFunc2:: ret ; stores hl in [wTrainerHeaderPtr] StoreTrainerHeaderPointer:: ld a, h ld [wTrainerHeaderPtr], a ld a, l ld [wTrainerHeaderPtr+1], a ret ; executes the current map script from the function pointer array provided in hl. ; a: map script index to execute (unless overridden by [wd733] bit 4) ExecuteCurMapScriptInTable:: push af push de call StoreTrainerHeaderPointer pop hl pop af push hl ld hl, wFlags_D733 bit 4, [hl] res 4, [hl] jr z, .useProvidedIndex ; test if map script index was overridden manually ld a, [wCurMapScript] .useProvidedIndex pop hl ld [wCurMapScript], a call CallFunctionInTable ld a, [wCurMapScript] ret LoadGymLeaderAndCityName:: push de ld de, wGymCityName ld bc, $11 call CopyData ; load city name pop hl ld de, wGymLeaderName ld bc, NAME_LENGTH jp CopyData ; load gym leader name ; reads specific information from trainer header (pointed to at wTrainerHeaderPtr) ; a: offset in header data ; 0 -> flag's bit (into wTrainerHeaderFlagBit) ; 2 -> flag's byte ptr (into hl) ; 4 -> before battle text (into hl) ; 6 -> after battle text (into hl) ; 8 -> end battle text (into hl) ReadTrainerHeaderInfo:: push de push af ld d, $0 ld e, a ld hl, wTrainerHeaderPtr ld a, [hli] ld l, [hl] ld h, a add hl, de pop af and a jr nz, .nonZeroOffset ld a, [hl] ld [wTrainerHeaderFlagBit], a ; store flag's bit jr .done .nonZeroOffset cp $2 jr z, .readPointer ; read flag's byte ptr cp $4 jr z, .readPointer ; read before battle text cp $6 jr z, .readPointer ; read after battle text cp $8 jr z, .readPointer ; read end battle text cp $a jr nz, .done ld a, [hli] ; read end battle text (2) but override the result afterwards (XXX why, bug?) ld d, [hl] ld e, a jr .done .readPointer ld a, [hli] ld h, [hl] ld l, a .done pop de ret TrainerFlagAction:: predef_jump FlagActionPredef TalkToTrainer:: call StoreTrainerHeaderPointer xor a call ReadTrainerHeaderInfo ; read flag's bit ld a, $2 call ReadTrainerHeaderInfo ; read flag's byte ptr ld a, [wTrainerHeaderFlagBit] ld c, a ld b, FLAG_TEST call TrainerFlagAction ; read trainer's flag ld a, c and a jr z, .trainerNotYetFought ; test trainer's flag ld a, $6 call ReadTrainerHeaderInfo ; print after battle text jp PrintText .trainerNotYetFought ld a, $4 call ReadTrainerHeaderInfo ; print before battle text call PrintText ld a, $a call ReadTrainerHeaderInfo ; (?) does nothing apparently (maybe bug in ReadTrainerHeaderInfo) push de ld a, $8 call ReadTrainerHeaderInfo ; read end battle text pop de call SaveEndBattleTextPointers ld hl, wFlags_D733 set 4, [hl] ; activate map script index override (index is set below) ld hl, wFlags_0xcd60 bit 0, [hl] ; test if player is already engaging the trainer (because the trainer saw the player) ret nz ; if the player talked to the trainer of his own volition call EngageMapTrainer ld hl, wCurMapScript inc [hl] ; increment map script index before StartTrainerBattle increments it again (next script function is usually EndTrainerBattle) jp StartTrainerBattle ; checks if any trainers are seeing the player and wanting to fight CheckFightingMapTrainers:: call CheckForEngagingTrainers ld a, [wSpriteIndex] cp $ff jr nz, .trainerEngaging xor a ld [wSpriteIndex], a ld [wTrainerHeaderFlagBit], a ret .trainerEngaging ld hl, wFlags_D733 set 3, [hl] ld [wEmotionBubbleSpriteIndex], a xor a ; EXCLAMATION_BUBBLE ld [wWhichEmotionBubble], a predef EmotionBubble ld a, D_RIGHT | D_LEFT | D_UP | D_DOWN ld [wJoyIgnore], a xor a ld [hJoyHeld], a call TrainerWalkUpToPlayer_Bank0 ld hl, wCurMapScript inc [hl] ; increment map script index (next script function is usually DisplayEnemyTrainerTextAndStartBattle) ret ; display the before battle text after the enemy trainer has walked up to the player's sprite DisplayEnemyTrainerTextAndStartBattle:: ld a, [wd730] and $1 ret nz ; return if the enemy trainer hasn't finished walking to the player's sprite ld [wJoyIgnore], a ld a, [wSpriteIndex] ld [hSpriteIndexOrTextID], a call DisplayTextID ; fall through StartTrainerBattle:: xor a ld [wJoyIgnore], a call InitBattleEnemyParameters ld hl, wd72d set 6, [hl] set 7, [hl] ld hl, wd72e set 1, [hl] ld hl, wCurMapScript inc [hl] ; increment map script index (next script function is usually EndTrainerBattle) ret EndTrainerBattle:: ld hl, wCurrentMapScriptFlags set 5, [hl] set 6, [hl] ld hl, wd72d res 7, [hl] ld hl, wFlags_0xcd60 res 0, [hl] ; player is no longer engaged by any trainer ld a, [wIsInBattle] cp $ff jp z, ResetButtonPressedAndMapScript ld a, $2 call ReadTrainerHeaderInfo ld a, [wTrainerHeaderFlagBit] ld c, a ld b, FLAG_SET call TrainerFlagAction ; flag trainer as fought ld a, [wEnemyMonOrTrainerClass] cp 200 jr nc, .skipRemoveSprite ; test if trainer was fought (in that case skip removing the corresponding sprite) ld hl, wMissableObjectList ld de, $2 ld a, [wSpriteIndex] call IsInArray ; search for sprite ID inc hl ld a, [hl] ld [wMissableObjectIndex], a ; load corresponding missable object index and remove it predef HideObject .skipRemoveSprite ld hl, wd730 bit 4, [hl] res 4, [hl] ret nz ResetButtonPressedAndMapScript:: xor a ld [wJoyIgnore], a ld [hJoyHeld], a ld [hJoyPressed], a ld [hJoyReleased], a ld [wCurMapScript], a ; reset battle status ret ; calls TrainerWalkUpToPlayer TrainerWalkUpToPlayer_Bank0:: jpba TrainerWalkUpToPlayer ; sets opponent type and mon set/lvl based on the engaging trainer data InitBattleEnemyParameters:: ld a, [wEngagedTrainerClass] ld [wCurOpponent], a ld [wEnemyMonOrTrainerClass], a cp 200 ld a, [wEngagedTrainerSet] jr c, .noTrainer ld [wTrainerNo], a ret .noTrainer ld [wCurEnemyLVL], a ret GetSpritePosition1:: ld hl, _GetSpritePosition1 jr SpritePositionBankswitch GetSpritePosition2:: ld hl, _GetSpritePosition2 jr SpritePositionBankswitch SetSpritePosition1:: ld hl, _SetSpritePosition1 jr SpritePositionBankswitch SetSpritePosition2:: ld hl, _SetSpritePosition2 SpritePositionBankswitch:: ld b, BANK(_GetSpritePosition1) ; BANK(_GetSpritePosition2), BANK(_SetSpritePosition1), BANK(_SetSpritePosition2) jp Bankswitch ; indirect jump to one of the four functions CheckForEngagingTrainers:: xor a call ReadTrainerHeaderInfo ; read trainer flag's bit (unused) ld d, h ; store trainer header address in de ld e, l .trainerLoop call StoreTrainerHeaderPointer ; set trainer header pointer to current trainer ld a, [de] ld [wSpriteIndex], a ; store trainer flag's bit ld [wTrainerHeaderFlagBit], a cp $ff ret z ld a, $2 call ReadTrainerHeaderInfo ; read trainer flag's byte ptr ld b, FLAG_TEST ld a, [wTrainerHeaderFlagBit] ld c, a call TrainerFlagAction ; read trainer flag ld a, c and a ; has the trainer already been defeated? jr nz, .continue push hl push de push hl xor a call ReadTrainerHeaderInfo ; get trainer header pointer inc hl ld a, [hl] ; read trainer engage distance pop hl ld [wTrainerEngageDistance], a ld a, [wSpriteIndex] swap a ld [wTrainerSpriteOffset], a predef TrainerEngage pop de pop hl ld a, [wTrainerSpriteOffset] and a ret nz ; break if the trainer is engaging .continue ld hl, $c add hl, de ld d, h ld e, l jr .trainerLoop ; hl = text if the player wins ; de = text if the player loses SaveEndBattleTextPointers:: ld a, [H_LOADEDROMBANK] ld [wEndBattleTextRomBank], a ld a, h ld [wEndBattleWinTextPointer], a ld a, l ld [wEndBattleWinTextPointer + 1], a ld a, d ld [wEndBattleLoseTextPointer], a ld a, e ld [wEndBattleLoseTextPointer + 1], a ret ; loads data of some trainer on the current map and plays pre-battle music ; [wSpriteIndex]: sprite ID of trainer who is engaged EngageMapTrainer:: ld hl, wMapSpriteExtraData ld d, $0 ld a, [wSpriteIndex] dec a add a ld e, a add hl, de ; seek to engaged trainer data ld a, [hli] ; load trainer class ld [wEngagedTrainerClass], a ld a, [hl] ; load trainer mon set ld [wEnemyMonAttackMod], a jp PlayTrainerMusic PrintEndBattleText:: push hl ld hl, wd72d bit 7, [hl] res 7, [hl] pop hl ret z ld a, [H_LOADEDROMBANK] push af ld a, [wEndBattleTextRomBank] ld [H_LOADEDROMBANK], a ld [MBC1RomBank], a push hl callba SaveTrainerName ld hl, TrainerEndBattleText call PrintText pop hl pop af ld [H_LOADEDROMBANK], a ld [MBC1RomBank], a callba FreezeEnemyTrainerSprite jp WaitForSoundToFinish GetSavedEndBattleTextPointer:: ld a, [wBattleResult] and a ; won battle jr nz, .lostBattle ld a, [wEndBattleWinTextPointer] ld h, a ld a, [wEndBattleWinTextPointer + 1] ld l, a ret .lostBattle ld a, [wEndBattleLoseTextPointer] ld h, a ld a, [wEndBattleLoseTextPointer + 1] ld l, a ret TrainerEndBattleText:: TX_FAR _TrainerNameText TX_ASM call GetSavedEndBattleTextPointer call TextCommandProcessor jp TextScriptEnd ; only engage withe trainer if the player is not already ; engaged with another trainer ; XXX unused? CheckIfAlreadyEngaged:: ld a, [wFlags_0xcd60] bit 0, a ret nz call EngageMapTrainer xor a ret PlayTrainerMusic:: ld a, [wEngagedTrainerClass] cp OPP_SONY1 ret z cp OPP_SONY2 ret z cp OPP_SONY3 ret z ld a, [wGymLeaderNo] and a ret nz xor a ld [wAudioFadeOutControl], a ld a, $ff call PlaySound ld a, BANK(Music_MeetEvilTrainer) ld [wAudioROMBank], a ld [wAudioSavedROMBank], a ld a, [wEngagedTrainerClass] ld b, a ld hl, EvilTrainerList .evilTrainerListLoop ld a, [hli] cp $ff jr z, .noEvilTrainer cp b jr nz, .evilTrainerListLoop ld a, MUSIC_MEET_EVIL_TRAINER jr .PlaySound .noEvilTrainer ld hl, FemaleTrainerList .femaleTrainerListLoop ld a, [hli] cp $ff jr z, .maleTrainer cp b jr nz, .femaleTrainerListLoop ld a, MUSIC_MEET_FEMALE_TRAINER jr .PlaySound .maleTrainer ld a, MUSIC_MEET_MALE_TRAINER .PlaySound ld [wNewSoundID], a jp PlaySound INCLUDE "data/trainer_types.asm" ; checks if the player's coordinates match an arrow movement tile's coordinates ; and if so, decodes the RLE movement data ; b = player Y ; c = player X DecodeArrowMovementRLE:: ld a, [hli] cp $ff ret z ; no match in the list cp b jr nz, .nextArrowMovementTileEntry1 ld a, [hli] cp c jr nz, .nextArrowMovementTileEntry2 ld a, [hli] ld d, [hl] ld e, a ld hl, wSimulatedJoypadStatesEnd call DecodeRLEList dec a ld [wSimulatedJoypadStatesIndex], a ret .nextArrowMovementTileEntry1 inc hl .nextArrowMovementTileEntry2 inc hl inc hl jr DecodeArrowMovementRLE FuncTX_ItemStoragePC:: call SaveScreenTilesToBuffer2 ld b, BANK(PlayerPC) ld hl, PlayerPC jr bankswitchAndContinue FuncTX_BillsPC:: call SaveScreenTilesToBuffer2 ld b, BANK(BillsPC_) ld hl, BillsPC_ jr bankswitchAndContinue FuncTX_GameCornerPrizeMenu:: ; XXX find a better name for this function ; special_F7 ld b,BANK(CeladonPrizeMenu) ld hl,CeladonPrizeMenu bankswitchAndContinue:: call Bankswitch jp HoldTextDisplayOpen ; continue to main text-engine function FuncTX_PokemonCenterPC:: ld b, BANK(ActivatePC) ld hl, ActivatePC jr bankswitchAndContinue StartSimulatingJoypadStates:: xor a ld [wOverrideSimulatedJoypadStatesMask], a ld [wSpriteStateData2 + $06], a ; player's sprite movement byte 1 ld hl, wd730 set 7, [hl] ret IsItemInBag:: ; given an item_id in b ; set zero flag if item isn't in player's bag ; else reset zero flag ; related to Pokémon Tower and ghosts predef GetQuantityOfItemInBag ld a,b and a ret DisplayPokedex:: ld [wd11e], a jpba _DisplayPokedex SetSpriteFacingDirectionAndDelay:: call SetSpriteFacingDirection ld c, 6 jp DelayFrames SetSpriteFacingDirection:: ld a, $9 ld [H_SPRITEDATAOFFSET], a call GetPointerWithinSpriteStateData1 ld a, [hSpriteFacingDirection] ld [hl], a ret SetSpriteImageIndexAfterSettingFacingDirection:: ld de, -7 add hl, de ld [hl], a ret ; tests if the player's coordinates are in a specified array ; INPUT: ; hl = address of array ; OUTPUT: ; [wCoordIndex] = if there is match, the matching array index ; sets carry if the coordinates are in the array, clears carry if not ArePlayerCoordsInArray:: ld a,[wYCoord] ld b,a ld a,[wXCoord] ld c,a ; fallthrough CheckCoords:: xor a ld [wCoordIndex],a .loop ld a,[hli] cp $ff ; reached terminator? jr z,.notInArray push hl ld hl,wCoordIndex inc [hl] pop hl .compareYCoord cp b jr z,.compareXCoord inc hl jr .loop .compareXCoord ld a,[hli] cp c jr nz,.loop .inArray scf ret .notInArray and a ret ; tests if a boulder's coordinates are in a specified array ; INPUT: ; hl = address of array ; [H_SPRITEINDEX] = index of boulder sprite ; OUTPUT: ; [wCoordIndex] = if there is match, the matching array index ; sets carry if the coordinates are in the array, clears carry if not CheckBoulderCoords:: push hl ld hl, wSpriteStateData2 + $04 ld a, [H_SPRITEINDEX] swap a ld d, $0 ld e, a add hl, de ld a, [hli] sub $4 ; because sprite coordinates are offset by 4 ld b, a ld a, [hl] sub $4 ; because sprite coordinates are offset by 4 ld c, a pop hl jp CheckCoords GetPointerWithinSpriteStateData1:: ld h, $c1 jr _GetPointerWithinSpriteStateData GetPointerWithinSpriteStateData2:: ld h, $c2 _GetPointerWithinSpriteStateData: ld a, [H_SPRITEDATAOFFSET] ld b, a ld a, [H_SPRITEINDEX] swap a add b ld l, a ret ; decodes a $ff-terminated RLEncoded list ; each entry is a pair of bytes ; the final $ff will be replicated in the output list and a contains the number of bytes written ; de: input list ; hl: output list DecodeRLEList:: xor a ld [wRLEByteCount], a ; count written bytes here .listLoop ld a, [de] cp $ff jr z, .endOfList ld [hRLEByteValue], a ; store byte value to be written inc de ld a, [de] ld b, $0 ld c, a ; number of bytes to be written ld a, [wRLEByteCount] add c ld [wRLEByteCount], a ; update total number of written bytes ld a, [hRLEByteValue] call FillMemory ; write a c-times to output inc de jr .listLoop .endOfList ld a, $ff ld [hl], a ; write final $ff ld a, [wRLEByteCount] inc a ; include sentinel in counting ret ; sets movement byte 1 for sprite [H_SPRITEINDEX] to $FE and byte 2 to [hSpriteMovementByte2] SetSpriteMovementBytesToFE:: push hl call GetSpriteMovementByte1Pointer ld [hl], $fe call GetSpriteMovementByte2Pointer ld a, [hSpriteMovementByte2] ld [hl], a pop hl ret ; sets both movement bytes for sprite [H_SPRITEINDEX] to $FF SetSpriteMovementBytesToFF:: push hl call GetSpriteMovementByte1Pointer ld [hl],$FF call GetSpriteMovementByte2Pointer ld [hl],$FF ; prevent person from walking? pop hl ret ; returns the sprite movement byte 1 pointer for sprite [H_SPRITEINDEX] in hl GetSpriteMovementByte1Pointer:: ld h,$C2 ld a,[H_SPRITEINDEX] swap a add 6 ld l,a ret ; returns the sprite movement byte 2 pointer for sprite [H_SPRITEINDEX] in hl GetSpriteMovementByte2Pointer:: push de ld hl,wMapSpriteData ld a,[H_SPRITEINDEX] dec a add a ld d,0 ld e,a add hl,de pop de ret GetTrainerInformation:: call GetTrainerName ld a, [wLinkState] and a jr nz, .linkBattle ld a, Bank(TrainerPicAndMoneyPointers) call BankswitchHome ld a, [wTrainerClass] dec a ld hl, TrainerPicAndMoneyPointers ld bc, $5 call AddNTimes ld de, wTrainerPicPointer ld a, [hli] ld [de], a inc de ld a, [hli] ld [de], a ld de, wTrainerBaseMoney ld a, [hli] ld [de], a inc de ld a, [hli] ld [de], a jp BankswitchBack .linkBattle ld hl, wTrainerPicPointer ld de, RedPicFront ld [hl], e inc hl ld [hl], d ret GetTrainerName:: jpba GetTrainerName_ HasEnoughMoney:: ; Check if the player has at least as much ; money as the 3-byte BCD value at hMoney. ld de, wPlayerMoney ld hl, hMoney ld c, 3 jp StringCmp HasEnoughCoins:: ; Check if the player has at least as many ; coins as the 2-byte BCD value at hCoins. ld de, wPlayerCoins ld hl, hCoins ld c, 2 jp StringCmp BankswitchHome:: ; switches to bank # in a ; Only use this when in the home bank! ld [wBankswitchHomeTemp],a ld a,[H_LOADEDROMBANK] ld [wBankswitchHomeSavedROMBank],a ld a,[wBankswitchHomeTemp] ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a ret BankswitchBack:: ; returns from BankswitchHome ld a,[wBankswitchHomeSavedROMBank] ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a ret Bankswitch:: ; self-contained bankswitch, use this when not in the home bank ; switches to the bank in b ld a,[H_LOADEDROMBANK] push af ld a,b ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a ld bc,.Return push bc jp [hl] .Return pop bc ld a,b ld [H_LOADEDROMBANK],a ld [MBC1RomBank],a ret ; displays yes/no choice ; yes -> set carry YesNoChoice:: call SaveScreenTilesToBuffer1 call InitYesNoTextBoxParameters jr DisplayYesNoChoice Func_35f4:: ld a, TWO_OPTION_MENU ld [wTextBoxID], a call InitYesNoTextBoxParameters jp DisplayTextBoxID InitYesNoTextBoxParameters:: xor a ; YES_NO_MENU ld [wTwoOptionMenuID], a coord hl, 14, 7 ld bc, $80f ret YesNoChoicePokeCenter:: call SaveScreenTilesToBuffer1 ld a, HEAL_CANCEL_MENU ld [wTwoOptionMenuID], a coord hl, 11, 6 lb bc, 8, 12 jr DisplayYesNoChoice WideYesNoChoice:: ; unused call SaveScreenTilesToBuffer1 ld a, WIDE_YES_NO_MENU ld [wTwoOptionMenuID], a coord hl, 12, 7 lb bc, 8, 13 DisplayYesNoChoice:: ld a, TWO_OPTION_MENU ld [wTextBoxID], a call DisplayTextBoxID jp LoadScreenTilesFromBuffer1 ; calculates the difference |a-b|, setting carry flag if a999) cp 999 / $100 + 1 jr nc, .overflow cp 999 / $100 jr c, .noOverflow ld a, [H_MULTIPLICAND+2] cp 999 % $100 + 1 jr c, .noOverflow .overflow ld a, 999 / $100 ; overflow: cap at 999 ld [H_MULTIPLICAND+1], a ld a, 999 % $100 ld [H_MULTIPLICAND+2], a .noOverflow pop bc pop de pop hl ret AddEnemyMonToPlayerParty:: ld a, [H_LOADEDROMBANK] push af ld a, BANK(_AddEnemyMonToPlayerParty) ld [H_LOADEDROMBANK], a ld [MBC1RomBank], a call _AddEnemyMonToPlayerParty pop bc ld a, b ld [H_LOADEDROMBANK], a ld [MBC1RomBank], a ret MoveMon:: ld a, [H_LOADEDROMBANK] push af ld a, BANK(_MoveMon) ld [H_LOADEDROMBANK], a ld [MBC1RomBank], a call _MoveMon pop bc ld a, b ld [H_LOADEDROMBANK], a ld [MBC1RomBank], a ret ; skips a text entries, each of size NAME_LENGTH (like trainer name, OT name, rival name, ...) ; hl: base pointer, will be incremented by NAME_LENGTH * a SkipFixedLengthTextEntries:: and a ret z ld bc, NAME_LENGTH .skipLoop add hl, bc dec a jr nz, .skipLoop ret AddNTimes:: ; add bc to hl a times and a ret z .loop add hl,bc dec a jr nz,.loop ret ; Compare strings, c bytes in length, at de and hl. ; Often used to compare big endian numbers in battle calculations. StringCmp:: ld a,[de] cp [hl] ret nz inc de inc hl dec c jr nz,StringCmp ret ; INPUT: ; a = oam block index (each block is 4 oam entries) ; b = Y coordinate of upper left corner of sprite ; c = X coordinate of upper left corner of sprite ; de = base address of 4 tile number and attribute pairs WriteOAMBlock:: ld h,wOAMBuffer / $100 swap a ; multiply by 16 ld l,a call .writeOneEntry ; upper left push bc ld a,8 add c ld c,a call .writeOneEntry ; upper right pop bc ld a,8 add b ld b,a call .writeOneEntry ; lower left ld a,8 add c ld c,a ; lower right .writeOneEntry ld [hl],b ; Y coordinate inc hl ld [hl],c ; X coordinate inc hl ld a,[de] ; tile number inc de ld [hli],a ld a,[de] ; attribute inc de ld [hli],a ret HandleMenuInput:: xor a ld [wPartyMenuAnimMonEnabled],a HandleMenuInput_:: ld a,[H_DOWNARROWBLINKCNT1] push af ld a,[H_DOWNARROWBLINKCNT2] push af ; save existing values on stack xor a ld [H_DOWNARROWBLINKCNT1],a ; blinking down arrow timing value 1 ld a,6 ld [H_DOWNARROWBLINKCNT2],a ; blinking down arrow timing value 2 .loop1 xor a ld [wAnimCounter],a ; counter for pokemon shaking animation call PlaceMenuCursor call Delay3 .loop2 push hl ld a,[wPartyMenuAnimMonEnabled] and a ; is it a pokemon selection menu? jr z,.getJoypadState callba AnimatePartyMon ; shake mini sprite of selected pokemon .getJoypadState pop hl call JoypadLowSensitivity ld a,[hJoy5] and a ; was a key pressed? jr nz,.keyPressed push hl coord hl, 18, 11 ; coordinates of blinking down arrow in some menus call HandleDownArrowBlinkTiming ; blink down arrow (if any) pop hl ld a,[wMenuJoypadPollCount] dec a jr z,.giveUpWaiting jr .loop2 .giveUpWaiting ; if a key wasn't pressed within the specified number of checks pop af ld [H_DOWNARROWBLINKCNT2],a pop af ld [H_DOWNARROWBLINKCNT1],a ; restore previous values xor a ld [wMenuWrappingEnabled],a ; disable menu wrapping ret .keyPressed xor a ld [wCheckFor180DegreeTurn],a ld a,[hJoy5] ld b,a bit 6,a ; pressed Up key? jr z,.checkIfDownPressed .upPressed ld a,[wCurrentMenuItem] ; selected menu item and a ; already at the top of the menu? jr z,.alreadyAtTop .notAtTop dec a ld [wCurrentMenuItem],a ; move selected menu item up one space jr .checkOtherKeys .alreadyAtTop ld a,[wMenuWrappingEnabled] and a ; is wrapping around enabled? jr z,.noWrappingAround ld a,[wMaxMenuItem] ld [wCurrentMenuItem],a ; wrap to the bottom of the menu jr .checkOtherKeys .checkIfDownPressed bit 7,a jr z,.checkOtherKeys .downPressed ld a,[wCurrentMenuItem] inc a ld c,a ld a,[wMaxMenuItem] cp c jr nc,.notAtBottom .alreadyAtBottom ld a,[wMenuWrappingEnabled] and a ; is wrapping around enabled? jr z,.noWrappingAround ld c,$00 ; wrap from bottom to top .notAtBottom ld a,c ld [wCurrentMenuItem],a .checkOtherKeys ld a,[wMenuWatchedKeys] and b ; does the menu care about any of the pressed keys? jp z,.loop1 .checkIfAButtonOrBButtonPressed ld a,[hJoy5] and A_BUTTON | B_BUTTON jr z,.skipPlayingSound .AButtonOrBButtonPressed push hl ld hl,wFlags_0xcd60 bit 5,[hl] pop hl jr nz,.skipPlayingSound ld a,SFX_PRESS_AB call PlaySound .skipPlayingSound pop af ld [H_DOWNARROWBLINKCNT2],a pop af ld [H_DOWNARROWBLINKCNT1],a ; restore previous values xor a ld [wMenuWrappingEnabled],a ; disable menu wrapping ld a,[hJoy5] ret .noWrappingAround ld a,[wMenuWatchMovingOutOfBounds] and a ; should we return if the user tried to go past the top or bottom? jr z,.checkOtherKeys jr .checkIfAButtonOrBButtonPressed PlaceMenuCursor:: ld a,[wTopMenuItemY] and a ; is the y coordinate 0? jr z,.adjustForXCoord coord hl, 0, 0 ld bc,SCREEN_WIDTH .topMenuItemLoop add hl,bc dec a jr nz,.topMenuItemLoop .adjustForXCoord ld a,[wTopMenuItemX] ld b,0 ld c,a add hl,bc push hl ld a,[wLastMenuItem] and a ; was the previous menu id 0? jr z,.checkForArrow1 push af ld a,[hFlags_0xFFF6] bit 1,a ; is the menu double spaced? jr z,.doubleSpaced1 ld bc,20 jr .getOldMenuItemScreenPosition .doubleSpaced1 ld bc,40 .getOldMenuItemScreenPosition pop af .oldMenuItemLoop add hl,bc dec a jr nz,.oldMenuItemLoop .checkForArrow1 ld a,[hl] cp a,"▶" ; was an arrow next to the previously selected menu item? jr nz,.skipClearingArrow .clearArrow ld a,[wTileBehindCursor] ld [hl],a .skipClearingArrow pop hl ld a,[wCurrentMenuItem] and a jr z,.checkForArrow2 push af ld a,[hFlags_0xFFF6] bit 1,a ; is the menu double spaced? jr z,.doubleSpaced2 ld bc,20 jr .getCurrentMenuItemScreenPosition .doubleSpaced2 ld bc,40 .getCurrentMenuItemScreenPosition pop af .currentMenuItemLoop add hl,bc dec a jr nz,.currentMenuItemLoop .checkForArrow2 ld a,[hl] cp "▶" ; has the right arrow already been placed? jr z,.skipSavingTile ; if so, don't lose the saved tile ld [wTileBehindCursor],a ; save tile before overwriting with right arrow .skipSavingTile ld a,"▶" ; place right arrow ld [hl],a ld a,l ld [wMenuCursorLocation],a ld a,h ld [wMenuCursorLocation + 1],a ld a,[wCurrentMenuItem] ld [wLastMenuItem],a ret ; This is used to mark a menu cursor other than the one currently being ; manipulated. In the case of submenus, this is used to show the location of ; the menu cursor in the parent menu. In the case of swapping items in list, ; this is used to mark the item that was first chosen to be swapped. PlaceUnfilledArrowMenuCursor:: ld b,a ld a,[wMenuCursorLocation] ld l,a ld a,[wMenuCursorLocation + 1] ld h,a ld [hl],$ec ; outline of right arrow ld a,b ret ; Replaces the menu cursor with a blank space. EraseMenuCursor:: ld a,[wMenuCursorLocation] ld l,a ld a,[wMenuCursorLocation + 1] ld h,a ld [hl]," " ret ; This toggles a blinking down arrow at hl on and off after a delay has passed. ; This is often called even when no blinking is occurring. ; The reason is that most functions that call this initialize H_DOWNARROWBLINKCNT1 to 0. ; The effect is that if the tile at hl is initialized with a down arrow, ; this function will toggle that down arrow on and off, but if the tile isn't ; initliazed with a down arrow, this function does nothing. ; That allows this to be called without worrying about if a down arrow should ; be blinking. HandleDownArrowBlinkTiming:: ld a,[hl] ld b,a ld a,"▼" cp b jr nz,.downArrowOff .downArrowOn ld a,[H_DOWNARROWBLINKCNT1] dec a ld [H_DOWNARROWBLINKCNT1],a ret nz ld a,[H_DOWNARROWBLINKCNT2] dec a ld [H_DOWNARROWBLINKCNT2],a ret nz ld a," " ld [hl],a ld a,$ff ld [H_DOWNARROWBLINKCNT1],a ld a,$06 ld [H_DOWNARROWBLINKCNT2],a ret .downArrowOff ld a,[H_DOWNARROWBLINKCNT1] and a ret z dec a ld [H_DOWNARROWBLINKCNT1],a ret nz dec a ld [H_DOWNARROWBLINKCNT1],a ld a,[H_DOWNARROWBLINKCNT2] dec a ld [H_DOWNARROWBLINKCNT2],a ret nz ld a,$06 ld [H_DOWNARROWBLINKCNT2],a ld a,"▼" ld [hl],a ret ; The following code either enables or disables the automatic drawing of ; text boxes by DisplayTextID. Both functions cause DisplayTextID to wait ; for a button press after displaying text (unless [wEnteringCableClub] is set). EnableAutoTextBoxDrawing:: xor a jr AutoTextBoxDrawingCommon DisableAutoTextBoxDrawing:: ld a,$01 AutoTextBoxDrawingCommon:: ld [wAutoTextBoxDrawingControl],a xor a ld [wDoNotWaitForButtonPressAfterDisplayingText],a ; make DisplayTextID wait for button press ret PrintText:: ; Print text hl at (1, 14). push hl ld a,MESSAGE_BOX ld [wTextBoxID],a call DisplayTextBoxID call UpdateSprites call Delay3 pop hl PrintText_NoCreatingTextBox:: coord bc, 1, 14 jp TextCommandProcessor PrintNumber:: ; Print the c-digit, b-byte value at de. ; Allows 2 to 7 digits. For 1-digit numbers, add ; the value to char "0" instead of calling PrintNumber. ; Flags LEADING_ZEROES and LEFT_ALIGN can be given ; in bits 7 and 6 of b respectively. push bc xor a ld [H_PASTLEADINGZEROES], a ld [H_NUMTOPRINT], a ld [H_NUMTOPRINT + 1], a ld a, b and $f cp 1 jr z, .byte cp 2 jr z, .word .long ld a, [de] ld [H_NUMTOPRINT], a inc de ld a, [de] ld [H_NUMTOPRINT + 1], a inc de ld a, [de] ld [H_NUMTOPRINT + 2], a jr .start .word ld a, [de] ld [H_NUMTOPRINT + 1], a inc de ld a, [de] ld [H_NUMTOPRINT + 2], a jr .start .byte ld a, [de] ld [H_NUMTOPRINT + 2], a .start push de ld d, b ld a, c ld b, a xor a ld c, a ld a, b cp 2 jr z, .tens cp 3 jr z, .hundreds cp 4 jr z, .thousands cp 5 jr z, .ten_thousands cp 6 jr z, .hundred_thousands print_digit: macro if (\1) / $10000 ld a, \1 / $10000 % $100 else xor a endc ld [H_POWEROFTEN + 0], a if (\1) / $100 ld a, \1 / $100 % $100 else xor a endc ld [H_POWEROFTEN + 1], a ld a, \1 / $1 % $100 ld [H_POWEROFTEN + 2], a call .PrintDigit call .NextDigit endm .millions print_digit 1000000 .hundred_thousands print_digit 100000 .ten_thousands print_digit 10000 .thousands print_digit 1000 .hundreds print_digit 100 .tens ld c, 0 ld a, [H_NUMTOPRINT + 2] .mod cp 10 jr c, .ok sub 10 inc c jr .mod .ok ld b, a ld a, [H_PASTLEADINGZEROES] or c ld [H_PASTLEADINGZEROES], a jr nz, .past call .PrintLeadingZero jr .next .past ld a, "0" add c ld [hl], a .next call .NextDigit .ones ld a, "0" add b ld [hli], a pop de dec de pop bc ret .PrintDigit: ; Divide by the current decimal place. ; Print the quotient, and keep the modulus. ld c, 0 .loop ld a, [H_POWEROFTEN] ld b, a ld a, [H_NUMTOPRINT] ld [H_SAVEDNUMTOPRINT], a cp b jr c, .underflow0 sub b ld [H_NUMTOPRINT], a ld a, [H_POWEROFTEN + 1] ld b, a ld a, [H_NUMTOPRINT + 1] ld [H_SAVEDNUMTOPRINT + 1], a cp b jr nc, .noborrow1 ld a, [H_NUMTOPRINT] or 0 jr z, .underflow1 dec a ld [H_NUMTOPRINT], a ld a, [H_NUMTOPRINT + 1] .noborrow1 sub b ld [H_NUMTOPRINT + 1], a ld a, [H_POWEROFTEN + 2] ld b, a ld a, [H_NUMTOPRINT + 2] ld [H_SAVEDNUMTOPRINT + 2], a cp b jr nc, .noborrow2 ld a, [H_NUMTOPRINT + 1] and a jr nz, .borrowed ld a, [H_NUMTOPRINT] and a jr z, .underflow2 dec a ld [H_NUMTOPRINT], a xor a .borrowed dec a ld [H_NUMTOPRINT + 1], a ld a, [H_NUMTOPRINT + 2] .noborrow2 sub b ld [H_NUMTOPRINT + 2], a inc c jr .loop .underflow2 ld a, [H_SAVEDNUMTOPRINT + 1] ld [H_NUMTOPRINT + 1], a .underflow1 ld a, [H_SAVEDNUMTOPRINT] ld [H_NUMTOPRINT], a .underflow0 ld a, [H_PASTLEADINGZEROES] or c jr z, .PrintLeadingZero ld a, "0" add c ld [hl], a ld [H_PASTLEADINGZEROES], a ret .PrintLeadingZero: bit BIT_LEADING_ZEROES, d ret z ld [hl], "0" ret .NextDigit: ; Increment unless the number is left-aligned, ; leading zeroes are not printed, and no digits ; have been printed yet. bit BIT_LEADING_ZEROES, d jr nz, .inc bit BIT_LEFT_ALIGN, d jr z, .inc ld a, [H_PASTLEADINGZEROES] and a ret z .inc inc hl ret CallFunctionInTable:: ; Call function a in jumptable hl. ; de is not preserved. push hl push de push bc add a ld d, 0 ld e, a add hl, de ld a, [hli] ld h, [hl] ld l, a ld de, .returnAddress push de jp [hl] .returnAddress pop bc pop de pop hl ret IsInArray:: ; Search an array at hl for the value in a. ; Entry size is de bytes. ; Return count b and carry if found. ld b, 0 IsInRestOfArray:: ld c, a .loop ld a, [hl] cp -1 jr z, .notfound cp c jr z, .found inc b add hl, de jr .loop .notfound and a ret .found scf ret RestoreScreenTilesAndReloadTilePatterns:: call ClearSprites ld a, $1 ld [wUpdateSpritesEnabled], a call ReloadMapSpriteTilePatterns call LoadScreenTilesFromBuffer2 call LoadTextBoxTilePatterns call RunDefaultPaletteCommand jr Delay3 GBPalWhiteOutWithDelay3:: call GBPalWhiteOut Delay3:: ; The bg map is updated each frame in thirds. ; Wait three frames to let the bg map fully update. ld c, 3 jp DelayFrames GBPalNormal:: ; Reset BGP and OBP0. ld a, %11100100 ; 3210 ld [rBGP], a ld a, %11010000 ; 3100 ld [rOBP0], a ret GBPalWhiteOut:: ; White out all palettes. xor a ld [rBGP],a ld [rOBP0],a ld [rOBP1],a ret RunDefaultPaletteCommand:: ld b,$ff RunPaletteCommand:: ld a,[wOnSGB] and a ret z predef_jump _RunPaletteCommand GetHealthBarColor:: ; Return at hl the palette of ; an HP bar e pixels long. ld a, e cp 27 ld d, 0 ; green jr nc, .gotColor cp 10 inc d ; yellow jr nc, .gotColor inc d ; red .gotColor ld [hl], d ret ; Copy the current map's sprites' tile patterns to VRAM again after they have ; been overwritten by other tile patterns. ReloadMapSpriteTilePatterns:: ld hl, wFontLoaded ld a, [hl] push af res 0, [hl] push hl xor a ld [wSpriteSetID], a call DisableLCD callba InitMapSprites call EnableLCD pop hl pop af ld [hl], a call LoadPlayerSpriteGraphics call LoadFontTilePatterns jp UpdateSprites GiveItem:: ; Give player quantity c of item b, ; and copy the item's name to wcf4b. ; Return carry on success. ld a, b ld [wd11e], a ld [wcf91], a ld a, c ld [wItemQuantity], a ld hl,wNumBagItems call AddItemToInventory ret nc call GetItemName call CopyStringToCF4B scf ret GivePokemon:: ; Give the player monster b at level c. ld a, b ld [wcf91], a ld a, c ld [wCurEnemyLVL], a xor a ; PLAYER_PARTY_DATA ld [wMonDataLocation], a jpba _GivePokemon Random:: ; Return a random number in a. ; For battles, use BattleRandom. push hl push de push bc callba Random_ ld a, [hRandomAdd] pop bc pop de pop hl ret INCLUDE "home/predef.asm" UpdateCinnabarGymGateTileBlocks:: jpba UpdateCinnabarGymGateTileBlocks_ CheckForHiddenObjectOrBookshelfOrCardKeyDoor:: ld a, [H_LOADEDROMBANK] push af ld a, [hJoyHeld] bit 0, a ; A button jr z, .nothingFound ; A button is pressed ld a, Bank(CheckForHiddenObject) ld [MBC1RomBank], a ld [H_LOADEDROMBANK], a call CheckForHiddenObject ld a, [$ffee] and a jr nz, .hiddenObjectNotFound ld a, [wHiddenObjectFunctionRomBank] ld [MBC1RomBank], a ld [H_LOADEDROMBANK], a ld de, .returnAddress push de jp [hl] .returnAddress xor a jr .done .hiddenObjectNotFound callba PrintBookshelfText ld a, [$ffdb] and a jr z, .done .nothingFound ld a, $ff .done ld [$ffeb], a pop af ld [MBC1RomBank], a ld [H_LOADEDROMBANK], a ret PrintPredefTextID:: ld [hSpriteIndexOrTextID], a ld hl, TextPredefs call SetMapTextPointer ld hl, wTextPredefFlag set 0, [hl] call DisplayTextID RestoreMapTextPointer:: ld hl, wMapTextPtr ld a, [$ffec] ld [hli], a ld a, [$ffec + 1] ld [hl], a ret SetMapTextPointer:: ld a, [wMapTextPtr] ld [$ffec], a ld a, [wMapTextPtr + 1] ld [$ffec + 1], a ld a, l ld [wMapTextPtr], a ld a, h ld [wMapTextPtr + 1], a ret TextPredefs:: const_value = 1 add_tx_pre CardKeySuccessText ; 01 add_tx_pre CardKeyFailText ; 02 add_tx_pre RedBedroomPCText ; 03 add_tx_pre RedBedroomSNESText ; 04 add_tx_pre PushStartText ; 05 add_tx_pre SaveOptionText ; 06 add_tx_pre StrengthsAndWeaknessesText ; 07 add_tx_pre OakLabEmailText ; 08 add_tx_pre AerodactylFossilText ; 09 add_tx_pre Route15UpstairsBinocularsText ; 0A add_tx_pre KabutopsFossilText ; 0B add_tx_pre GymStatueText1 ; 0C add_tx_pre GymStatueText2 ; 0D add_tx_pre BookcaseText ; 0E add_tx_pre ViridianCityPokecenterBenchGuyText ; 0F add_tx_pre PewterCityPokecenterBenchGuyText ; 10 add_tx_pre CeruleanCityPokecenterBenchGuyText ; 11 add_tx_pre LavenderCityPokecenterBenchGuyText ; 12 add_tx_pre VermilionCityPokecenterBenchGuyText ; 13 add_tx_pre CeladonCityPokecenterBenchGuyText ; 14 add_tx_pre CeladonCityHotelText ; 15 add_tx_pre FuchsiaCityPokecenterBenchGuyText ; 16 add_tx_pre CinnabarIslandPokecenterBenchGuyText ; 17 add_tx_pre SaffronCityPokecenterBenchGuyText ; 18 add_tx_pre MtMoonPokecenterBenchGuyText ; 19 add_tx_pre RockTunnelPokecenterBenchGuyText ; 1A add_tx_pre UnusedBenchGuyText1 ; 1B XXX unused add_tx_pre UnusedBenchGuyText2 ; 1C XXX unused add_tx_pre UnusedBenchGuyText3 ; 1D XXX unused add_tx_pre UnusedPredefText ; 1E XXX unused add_tx_pre PokemonCenterPCText ; 1F add_tx_pre ViridianSchoolNotebook ; 20 add_tx_pre ViridianSchoolBlackboard ; 21 add_tx_pre JustAMomentText ; 22 add_tx_pre OpenBillsPCText ; 23 add_tx_pre FoundHiddenItemText ; 24 add_tx_pre HiddenItemBagFullText ; 25 XXX unused add_tx_pre VermilionGymTrashText ; 26 add_tx_pre IndigoPlateauHQText ; 27 add_tx_pre GameCornerOutOfOrderText ; 28 add_tx_pre GameCornerOutToLunchText ; 29 add_tx_pre GameCornerSomeonesKeysText ; 2A add_tx_pre FoundHiddenCoinsText ; 2B add_tx_pre DroppedHiddenCoinsText ; 2C add_tx_pre BillsHouseMonitorText ; 2D add_tx_pre BillsHouseInitiatedText ; 2E add_tx_pre BillsHousePokemonList ; 2F add_tx_pre MagazinesText ; 30 add_tx_pre CinnabarGymQuiz ; 31 add_tx_pre GameCornerNoCoinsText ; 32 add_tx_pre GameCornerCoinCaseText ; 33 add_tx_pre LinkCableHelp ; 34 add_tx_pre TMNotebook ; 35 add_tx_pre FightingDojoText ; 36 add_tx_pre EnemiesOnEverySideText ; 37 add_tx_pre WhatGoesAroundComesAroundText ; 38 add_tx_pre NewBicycleText ; 39 add_tx_pre IndigoPlateauStatues ; 3A add_tx_pre VermilionGymTrashSuccessText1 ; 3B add_tx_pre VermilionGymTrashSuccessText2 ; 3C XXX unused add_tx_pre VermilionGymTrashSuccessText3 ; 3D add_tx_pre VermilionGymTrashFailText ; 3E add_tx_pre TownMapText ; 3F add_tx_pre BookOrSculptureText ; 40 add_tx_pre ElevatorText ; 41 add_tx_pre PokemonStuffText ; 42