diff options
Diffstat (limited to 'de/home.asm')
-rw-r--r-- | de/home.asm | 4732 |
1 files changed, 4732 insertions, 0 deletions
diff --git a/de/home.asm b/de/home.asm new file mode 100644 index 00000000..57781e05 --- /dev/null +++ b/de/home.asm @@ -0,0 +1,4732 @@ + +; 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 is 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,"B" + ld [hli],a + ld a,"S" + ld [hli],a + ld [hl],"G" + 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 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" + inclang 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 + ld a,[wd795] + bit 7,a + jr z,.didnotblackoutinsafari + xor a + ld [wNumSafariBalls],a + ld [wSafariSteps],a + ld [wSafariSteps+1],a + ld [wd795],a + ld [wcf0d],a + ld [wSafariZoneEntranceCurScript],a +.didnotblackoutinsafari + 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 CopyStringToCF50 ; copy name to wcf50 + 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 "ZURÜCK@" + +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 "VM" + +; 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: unsuccessful +; 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 [wEngagedTrainerSet], 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 <byte value> <repetitions> +; 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, 13, 7 + ld bc, $80e + 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 a<b +CalcDifference:: + sub b + ret nc + cpl + add $1 + scf + ret + +MoveSprite:: +; move the sprite [H_SPRITEINDEX] with the movement pointed to by de +; actually only copies the movement data to wNPCMovementDirections for later + call SetSpriteMovementBytesToFF +MoveSprite_:: + push hl + push bc + call GetSpriteMovementByte1Pointer + xor a + ld [hl],a + ld hl,wNPCMovementDirections + ld c,0 + +.loop + ld a,[de] + ld [hli],a + inc de + inc c + cp $FF ; have we reached the end of the movement data? + jr nz,.loop + + ld a,c + ld [wNPCNumScriptedSteps],a ; number of steps taken + + pop bc + ld hl,wd730 + set 0,[hl] + pop hl + xor a + ld [wOverrideSimulatedJoypadStatesMask],a + ld [wSimulatedJoypadStatesEnd],a + dec a + ld [wJoyIgnore],a + ld [wWastedByteCD3A],a + ret + +; divides [hDividend2] by [hDivisor2] and stores the quotient in [hQuotient2] +DivideBytes:: + push hl + ld hl, hQuotient2 + xor a + ld [hld], a + ld a, [hld] + and a + jr z, .done + ld a, [hli] +.loop + sub [hl] + jr c, .done + inc hl + inc [hl] + dec hl + jr .loop +.done + pop hl + ret + + +LoadFontTilePatterns:: + ld a, [rLCDC] + bit 7, a ; is the LCD enabled? + jr nz, .on +.off + ld hl, FontGraphics + ld de, vFont + ld bc, FontGraphicsEnd - FontGraphics + ld a, BANK(FontGraphics) + jp FarCopyDataDouble ; if LCD is off, transfer all at once +.on + ld de, FontGraphics + ld hl, vFont + lb bc, BANK(FontGraphics), (FontGraphicsEnd - FontGraphics) / $8 + jp CopyVideoDataDouble ; if LCD is on, transfer during V-blank + +LoadTextBoxTilePatterns:: + ld a, [rLCDC] + bit 7, a ; is the LCD enabled? + jr nz, .on +.off + ld hl, TextBoxGraphics + ld de, vChars2 + $600 + ld bc, TextBoxGraphicsEnd - TextBoxGraphics + ld a, BANK(TextBoxGraphics) + jp FarCopyData2 ; if LCD is off, transfer all at once +.on + ld de, TextBoxGraphics + ld hl, vChars2 + $600 + lb bc, BANK(TextBoxGraphics), (TextBoxGraphicsEnd - TextBoxGraphics) / $10 + jp CopyVideoData ; if LCD is on, transfer during V-blank + +LoadHpBarAndStatusTilePatterns:: + ld a, [rLCDC] + bit 7, a ; is the LCD enabled? + jr nz, .on +.off + ld hl, HpBarAndStatusGraphics + ld de, vChars2 + $620 + ld bc, HpBarAndStatusGraphicsEnd - HpBarAndStatusGraphics + ld a, BANK(HpBarAndStatusGraphics) + jp FarCopyData2 ; if LCD is off, transfer all at once +.on + ld de, HpBarAndStatusGraphics + ld hl, vChars2 + $620 + lb bc, BANK(HpBarAndStatusGraphics), (HpBarAndStatusGraphicsEnd - HpBarAndStatusGraphics) / $10 + jp CopyVideoData ; if LCD is on, transfer during V-blank + + +FillMemory:: +; Fill bc bytes at hl with a. + push de + ld d, a +.loop + ld a, d + ld [hli], a + dec bc + ld a, b + or c + jr nz, .loop + pop de + ret + + +UncompressSpriteFromDE:: +; Decompress pic at a:de. + ld hl, wSpriteInputPtr + ld [hl], e + inc hl + ld [hl], d + jp UncompressSpriteData + +SaveScreenTilesToBuffer2:: + coord hl, 0, 0 + ld de, wTileMapBackup2 + ld bc, SCREEN_WIDTH * SCREEN_HEIGHT + call CopyData + ret + +LoadScreenTilesFromBuffer2:: + call LoadScreenTilesFromBuffer2DisableBGTransfer + ld a, 1 + ld [H_AUTOBGTRANSFERENABLED], a + ret + +; loads screen tiles stored in wTileMapBackup2 but leaves H_AUTOBGTRANSFERENABLED disabled +LoadScreenTilesFromBuffer2DisableBGTransfer:: + xor a + ld [H_AUTOBGTRANSFERENABLED], a + ld hl, wTileMapBackup2 + coord de, 0, 0 + ld bc, SCREEN_WIDTH * SCREEN_HEIGHT + call CopyData + ret + +SaveScreenTilesToBuffer1:: + coord hl, 0, 0 + ld de, wTileMapBackup + ld bc, SCREEN_WIDTH * SCREEN_HEIGHT + jp CopyData + +LoadScreenTilesFromBuffer1:: + xor a + ld [H_AUTOBGTRANSFERENABLED], a + ld hl, wTileMapBackup + coord de, 0, 0 + ld bc, SCREEN_WIDTH * SCREEN_HEIGHT + call CopyData + ld a, 1 + ld [H_AUTOBGTRANSFERENABLED], a + ret + +DelayFrames:: +; wait c frames + call DelayFrame + dec c + jr nz,DelayFrames + ret + +PlaySoundWaitForCurrent:: + push af + call WaitForSoundToFinish + pop af + jp PlaySound + +; Wait for sound to finish playing +WaitForSoundToFinish:: + ld a, [wLowHealthAlarm] + and $80 + ret nz + push hl +.waitLoop + ld hl, wChannelSoundIDs + Ch4 + xor a + or [hl] + inc hl + or [hl] + inc hl + inc hl + or [hl] + jr nz, .waitLoop + pop hl + ret + +NamePointers:: + dw MonsterNames + dw MoveNames + dw UnusedNames + dw ItemNames + dw wPartyMonOT ; player's OT names list + dw wEnemyMonOT ; enemy's OT names list + dw TrainerNames + +GetName:: +; arguments: +; [wd0b5] = which name +; [wNameListType] = which list +; [wPredefBank] = bank of list +; +; returns pointer to name in de + ld a,[wd0b5] + ld [wd11e],a + + ; TM names are separate from item names. + ; BUG: This applies to all names instead of just items. + cp HM_01 + jp nc, GetMachineName + + ld a,[H_LOADEDROMBANK] + push af + push hl + push bc + push de + ld a,[wNameListType] ;List3759_entrySelector + dec a + jr nz,.otherEntries + ;1 = MON_NAMES + call GetMonName + ld hl,NAME_LENGTH + add hl,de + ld e,l + ld d,h + jr .gotPtr +.otherEntries + ;2-7 = OTHER ENTRIES + ld a,[wPredefBank] + ld [H_LOADEDROMBANK],a + ld [MBC1RomBank],a + ld a,[wNameListType] ;VariousNames' entryID + dec a + add a + ld d,0 + ld e,a + jr nc,.skip + inc d +.skip + ld hl,NamePointers + add hl,de + ld a,[hli] + ld [$ff96],a + ld a,[hl] + ld [$ff95],a + ld a,[$ff95] + ld h,a + ld a,[$ff96] + ld l,a + ld a,[wd0b5] + ld b,a + ld c,0 +.nextName + ld d,h + ld e,l +.nextChar + ld a,[hli] + cp "@" + jr nz,.nextChar + inc c ;entry counter + ld a,b ;wanted entry + cp c + jr nz,.nextName + ld h,d + ld l,e + ld de,wcd6d + ld bc,$0014 + call CopyData +.gotPtr + ld a,e + ld [wUnusedCF8D],a + ld a,d + ld [wUnusedCF8D + 1],a + pop de + pop bc + pop hl + pop af + ld [H_LOADEDROMBANK],a + ld [MBC1RomBank],a + ret + +GetItemPrice:: +; Stores item's price as BCD at hItemPrice (3 bytes) +; Input: [wcf91] = item id + ld a, [H_LOADEDROMBANK] + push af + ld a, [wListMenuID] + cp MOVESLISTMENU + ld a, BANK(ItemPrices) + jr nz, .ok + ld a, $f ; hardcoded Bank +.ok + ld [H_LOADEDROMBANK], a + ld [MBC1RomBank], a + ld hl, wItemPrices + ld a, [hli] + ld h, [hl] + ld l, a + ld a, [wcf91] ; a contains item id + cp HM_01 + jr nc, .getTMPrice + ld bc, $3 +.loop + add hl, bc + dec a + jr nz, .loop + dec hl + ld a, [hld] + ld [hItemPrice + 2], a + ld a, [hld] + ld [hItemPrice + 1], a + ld a, [hl] + ld [hItemPrice], a + jr .done +.getTMPrice + ld a, Bank(GetMachinePrice) + ld [H_LOADEDROMBANK], a + ld [MBC1RomBank], a + call GetMachinePrice +.done + ld de, hItemPrice + pop af + ld [H_LOADEDROMBANK], a + ld [MBC1RomBank], a + ret + +; copies a string from [de] to [wcf50] +CopyStringToCF50:: + ld hl, wcf50 + ; fall through + +; copies a string from [de] to [hl] +CopyString:: + ld a, [de] + inc de + ld [hli], a + cp "@" + jr nz, CopyString + ret + +; this function is used when lower button sensitivity is wanted (e.g. menus) +; OUTPUT: [hJoy5] = pressed buttons in usual format +; there are two flags that control its functionality, [hJoy6] and [hJoy7] +; there are essentially three modes of operation +; 1. Get newly pressed buttons only +; ([hJoy7] == 0, [hJoy6] == any) +; Just copies [hJoyPressed] to [hJoy5]. +; 2. Get currently pressed buttons at low sample rate with delay +; ([hJoy7] == 1, [hJoy6] != 0) +; If the user holds down buttons for more than half a second, +; report buttons as being pressed up to 12 times per second thereafter. +; If the user holds down buttons for less than half a second, +; report only one button press. +; 3. Same as 2, but report no buttons as pressed if A or B is held down. +; ([hJoy7] == 1, [hJoy6] == 0) +JoypadLowSensitivity:: + call Joypad + ld a,[hJoy7] ; flag + and a ; get all currently pressed buttons or only newly pressed buttons? + ld a,[hJoyPressed] ; newly pressed buttons + jr z,.storeButtonState + ld a,[hJoyHeld] ; all currently pressed buttons +.storeButtonState + ld [hJoy5],a + ld a,[hJoyPressed] ; newly pressed buttons + and a ; have any buttons been newly pressed since last check? + jr z,.noNewlyPressedButtons +.newlyPressedButtons + ld a,30 ; half a second delay + ld [H_FRAMECOUNTER],a + ret +.noNewlyPressedButtons + ld a,[H_FRAMECOUNTER] + and a ; is the delay over? + jr z,.delayOver +.delayNotOver + xor a + ld [hJoy5],a ; report no buttons as pressed + ret +.delayOver +; if [hJoy6] = 0 and A or B is pressed, report no buttons as pressed + ld a,[hJoyHeld] + and A_BUTTON | B_BUTTON + jr z,.setShortDelay + ld a,[hJoy6] ; flag + and a + jr nz,.setShortDelay + xor a + ld [hJoy5],a +.setShortDelay + ld a,5 ; 1/12 of a second delay + ld [H_FRAMECOUNTER],a + ret + +WaitForTextScrollButtonPress:: + ld a, [H_DOWNARROWBLINKCNT1] + push af + ld a, [H_DOWNARROWBLINKCNT2] + push af + xor a + ld [H_DOWNARROWBLINKCNT1], a + ld a, $6 + ld [H_DOWNARROWBLINKCNT2], a +.loop + push hl + ld a, [wTownMapSpriteBlinkingEnabled] + and a + jr z, .skipAnimation + call TownMapSpriteBlinkingAnimation +.skipAnimation + coord hl, 18, 16 + call HandleDownArrowBlinkTiming + pop hl + call JoypadLowSensitivity + predef CableClub_Run + ld a, [hJoy5] + and A_BUTTON | B_BUTTON + jr z, .loop + pop af + ld [H_DOWNARROWBLINKCNT2], a + pop af + ld [H_DOWNARROWBLINKCNT1], a + ret + +; (unless in link battle) waits for A or B being pressed and outputs the scrolling sound effect +ManualTextScroll:: + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr z, .inLinkBattle + call WaitForTextScrollButtonPress + ld a, SFX_PRESS_AB + jp PlaySound +.inLinkBattle + ld c, 65 + jp DelayFrames + +; function to do multiplication +; all values are big endian +; INPUT +; FF96-FF98 = multiplicand +; FF99 = multiplier +; OUTPUT +; FF95-FF98 = product +Multiply:: + push hl + push bc + callab _Multiply + pop bc + pop hl + ret + +; function to do division +; all values are big endian +; INPUT +; FF95-FF98 = dividend +; FF99 = divisor +; b = number of bytes in the dividend (starting from FF95) +; OUTPUT +; FF95-FF98 = quotient +; FF99 = remainder +Divide:: + push hl + push de + push bc + ld a,[H_LOADEDROMBANK] + push af + ld a,Bank(_Divide) + ld [H_LOADEDROMBANK],a + ld [MBC1RomBank],a + call _Divide + pop af + ld [H_LOADEDROMBANK],a + ld [MBC1RomBank],a + pop bc + pop de + pop hl + ret + +; This function is used to wait a short period after printing a letter to the +; screen unless the player presses the A/B button or the delay is turned off +; through the [wd730] or [wLetterPrintingDelayFlags] flags. +PrintLetterDelay:: + ld a,[wd730] + bit 6,a + ret nz + ld a,[wLetterPrintingDelayFlags] + bit 1,a + ret z + push hl + push de + push bc + ld a,[wLetterPrintingDelayFlags] + bit 0,a + jr z,.waitOneFrame + ld a,[wOptions] + and $f + ld [H_FRAMECOUNTER],a + jr .checkButtons +.waitOneFrame + ld a,1 + ld [H_FRAMECOUNTER],a +.checkButtons + call Joypad + ld a,[hJoyHeld] +.checkAButton + bit 0,a ; is the A button pressed? + jr z,.checkBButton + jr .endWait +.checkBButton + bit 1,a ; is the B button pressed? + jr z,.buttonsNotPressed +.endWait + call DelayFrame + jr .done +.buttonsNotPressed ; if neither A nor B is pressed + ld a,[H_FRAMECOUNTER] + and a + jr nz,.checkButtons +.done + pop bc + pop de + pop hl + ret + +; Copies [hl, bc) to [de, bc - hl). +; In other words, the source data is from hl up to but not including bc, +; and the destination is de. +CopyDataUntil:: + ld a,[hli] + ld [de],a + inc de + ld a,h + cp b + jr nz,CopyDataUntil + ld a,l + cp c + jr nz,CopyDataUntil + ret + +; Function to remove a pokemon from the party or the current box. +; wWhichPokemon determines the pokemon. +; [wRemoveMonFromBox] == 0 specifies the party. +; [wRemoveMonFromBox] != 0 specifies the current box. +RemovePokemon:: + jpab _RemovePokemon + +AddPartyMon:: + push hl + push de + push bc + callba _AddPartyMon + pop bc + pop de + pop hl + ret + +; calculates all 5 stats of current mon and writes them to [de] +CalcStats:: + ld c, $0 +.statsLoop + inc c + call CalcStat + ld a, [H_MULTIPLICAND+1] + ld [de], a + inc de + ld a, [H_MULTIPLICAND+2] + ld [de], a + inc de + ld a, c + cp NUM_STATS + jr nz, .statsLoop + ret + +; calculates stat c of current mon +; c: stat to calc (HP=1,Atk=2,Def=3,Spd=4,Spc=5) +; b: consider stat exp? +; hl: base ptr to stat exp values ([hl + 2*c - 1] and [hl + 2*c]) +CalcStat:: + push hl + push de + push bc + ld a, b + ld d, a + push hl + ld hl, wMonHeader + ld b, $0 + add hl, bc + ld a, [hl] ; read base value of stat + ld e, a + pop hl + push hl + sla c + ld a, d + and a + jr z, .statExpDone ; consider stat exp? + add hl, bc ; skip to corresponding stat exp value +.statExpLoop ; calculates ceil(Sqrt(stat exp)) in b + xor a + ld [H_MULTIPLICAND], a + ld [H_MULTIPLICAND+1], a + inc b ; increment current stat exp bonus + ld a, b + cp $ff + jr z, .statExpDone + ld [H_MULTIPLICAND+2], a + ld [H_MULTIPLIER], a + call Multiply + ld a, [hld] + ld d, a + ld a, [$ff98] + sub d + ld a, [hli] + ld d, a + ld a, [$ff97] + sbc d ; test if (current stat exp bonus)^2 < stat exp + jr c, .statExpLoop +.statExpDone + srl c + pop hl + push bc + ld bc, wPartyMon1DVs - (wPartyMon1HPExp - 1) ; also wEnemyMonDVs - wEnemyMonHP + add hl, bc + pop bc + ld a, c + cp $2 + jr z, .getAttackIV + cp $3 + jr z, .getDefenseIV + cp $4 + jr z, .getSpeedIV + cp $5 + jr z, .getSpecialIV +.getHpIV + push bc + ld a, [hl] ; Atk IV + swap a + and $1 + sla a + sla a + sla a + ld b, a + ld a, [hli] ; Def IV + and $1 + sla a + sla a + add b + ld b, a + ld a, [hl] ; Spd IV + swap a + and $1 + sla a + add b + ld b, a + ld a, [hl] ; Spc IV + and $1 + add b ; HP IV: LSB of the other 4 IVs + pop bc + jr .calcStatFromIV +.getAttackIV + ld a, [hl] + swap a + and $f + jr .calcStatFromIV +.getDefenseIV + ld a, [hl] + and $f + jr .calcStatFromIV +.getSpeedIV + inc hl + ld a, [hl] + swap a + and $f + jr .calcStatFromIV +.getSpecialIV + inc hl + ld a, [hl] + and $f +.calcStatFromIV + ld d, $0 + add e + ld e, a + jr nc, .noCarry + inc d ; de = Base + IV +.noCarry + sla e + rl d ; de = (Base + IV) * 2 + srl b + srl b ; b = ceil(Sqrt(stat exp)) / 4 + ld a, b + add e + jr nc, .noCarry2 + inc d ; de = (Base + IV) * 2 + ceil(Sqrt(stat exp)) / 4 +.noCarry2 + ld [H_MULTIPLICAND+2], a + ld a, d + ld [H_MULTIPLICAND+1], a + xor a + ld [H_MULTIPLICAND], a + ld a, [wCurEnemyLVL] + ld [H_MULTIPLIER], a + call Multiply ; ((Base + IV) * 2 + ceil(Sqrt(stat exp)) / 4) * Level + ld a, [H_MULTIPLICAND] + ld [H_DIVIDEND], a + ld a, [H_MULTIPLICAND+1] + ld [H_DIVIDEND+1], a + ld a, [H_MULTIPLICAND+2] + ld [H_DIVIDEND+2], a + ld a, $64 + ld [H_DIVISOR], a + ld a, $3 + ld b, a + call Divide ; (((Base + IV) * 2 + ceil(Sqrt(stat exp)) / 4) * Level) / 100 + ld a, c + cp $1 + ld a, 5 ; + 5 for non-HP stat + jr nz, .notHPStat + ld a, [wCurEnemyLVL] + ld b, a + ld a, [H_MULTIPLICAND+2] + add b + ld [H_MULTIPLICAND+2], a + jr nc, .noCarry3 + ld a, [H_MULTIPLICAND+1] + inc a + ld [H_MULTIPLICAND+1], a ; HP: (((Base + IV) * 2 + ceil(Sqrt(stat exp)) / 4) * Level) / 100 + Level +.noCarry3 + ld a, 10 ; +10 for HP stat +.notHPStat + ld b, a + ld a, [H_MULTIPLICAND+2] + add b + ld [H_MULTIPLICAND+2], a + jr nc, .noCarry4 + ld a, [H_MULTIPLICAND+1] + inc a ; non-HP: (((Base + IV) * 2 + ceil(Sqrt(stat exp)) / 4) * Level) / 100 + 5 + ld [H_MULTIPLICAND+1], a ; HP: (((Base + IV) * 2 + ceil(Sqrt(stat exp)) / 4) * Level) / 100 + Level + 10 +.noCarry4 + ld a, [H_MULTIPLICAND+1] ; check for overflow (>999) + 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 +; initialized 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 wcf50. +; 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 CopyStringToCF50 + 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 |