pokered-rematch/home.asm

4722 lines
95 KiB
NASM
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

; 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, "F"
ld [hli], a
ld a, "N"
ld [hli], a
ld [hl], "T"
and a
ret
PrintStatusConditionNotFainted:
ld a, [H_LOADEDROMBANK]
push af
ld a, BANK(PrintStatusAilment)
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
call PrintStatusAilment ; print status condition
pop bc
ld a, b
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
ret
; function to print pokemon level, leaving off the ":L" if the level is at least 100
; INPUT:
; hl = destination address
; [wLoadedMonLevel] = level
PrintLevel::
ld a, $6e ; ":L" tile ID
ld [hli], a
ld c, 2 ; number of digits
ld a, [wLoadedMonLevel] ; level
cp 100
jr c, PrintLevelCommon
; if level at least 100, write over the ":L" tile
dec hl
inc c ; increment number of digits to 3
jr PrintLevelCommon
; prints the level without leaving off ":L" regardless of level
; INPUT:
; hl = destination address
; [wLoadedMonLevel] = level
PrintLevelFull::
ld a, $6e ; ":L" tile ID
ld [hli], a
ld c, 3 ; number of digits
ld a, [wLoadedMonLevel] ; level
PrintLevelCommon::
ld [wd11e], a
ld de, wd11e
ld b, LEFT_ALIGN | 1 ; 1 byte
jp PrintNumber
GetwMoves::
; Unused. Returns the move at index a from wMoves in a
ld hl, wMoves
ld c, a
ld b, 0
add hl, bc
ld a, [hl]
ret
; copies the base stat data of a pokemon to wMonHeader
; INPUT:
; [wd0b5] = pokemon ID
GetMonHeader::
ld a, [H_LOADEDROMBANK]
push af
ld a, BANK(BaseStats)
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
push bc
push de
push hl
ld a, [wd11e]
push af
ld a, [wd0b5]
ld [wd11e], a
ld de, FossilKabutopsPic
ld b, $66 ; size of Kabutops fossil and Ghost sprites
cp FOSSIL_KABUTOPS ; Kabutops fossil
jr z, .specialID
ld de, GhostPic
cp MON_GHOST ; Ghost
jr z, .specialID
ld de, FossilAerodactylPic
ld b, $77 ; size of Aerodactyl fossil sprite
cp FOSSIL_AERODACTYL ; Aerodactyl fossil
jr z, .specialID
cp 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"
INCLUDE "home/text.asm"
INCLUDE "home/vcopy.asm"
INCLUDE "home/init.asm"
INCLUDE "home/vblank.asm"
INCLUDE "home/fade.asm"
INCLUDE "home/serial.asm"
INCLUDE "home/timer.asm"
INCLUDE "home/audio.asm"
UpdateSprites::
ld a, [wUpdateSpritesEnabled]
dec a
ret nz
ld a, [H_LOADEDROMBANK]
push af
ld a, Bank(_UpdateSprites)
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
call _UpdateSprites
pop af
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
ret
INCLUDE "data/mart_inventories.asm"
TextScriptEndingChar::
db "@"
TextScriptEnd::
ld hl, TextScriptEndingChar
ret
ExclamationText::
TX_FAR _ExclamationText
db "@"
GroundRoseText::
TX_FAR _GroundRoseText
db "@"
BoulderText::
TX_FAR _BoulderText
db "@"
MartSignText::
TX_FAR _MartSignText
db "@"
PokeCenterSignText::
TX_FAR _PokeCenterSignText
db "@"
PickUpItemText::
TX_ASM
predef PickUpItem
jp TextScriptEnd
INCLUDE "home/pic.asm"
ResetPlayerSpriteData::
ld hl, wSpriteStateData1
call ResetPlayerSpriteData_ClearSpriteData
ld hl, wSpriteStateData2
call ResetPlayerSpriteData_ClearSpriteData
ld a, $1
ld [wSpriteStateData1], a
ld [wSpriteStateData2 + $0e], a
ld hl, wSpriteStateData1 + 4
ld [hl], $3c ; set Y screen pos
inc hl
inc hl
ld [hl], $40 ; set X screen pos
ret
; overwrites sprite data with zeroes
ResetPlayerSpriteData_ClearSpriteData::
ld bc, $10
xor a
jp FillMemory
FadeOutAudio::
ld a, [wAudioFadeOutControl]
and a ; currently fading out audio?
jr nz, .fadingOut
ld a, [wd72c]
bit 1, a
ret nz
ld a, $77
ld [rNR50], a
ret
.fadingOut
ld a, [wAudioFadeOutCounter]
and a
jr z, .counterReachedZero
dec a
ld [wAudioFadeOutCounter], a
ret
.counterReachedZero
ld a, [wAudioFadeOutCounterReloadValue]
ld [wAudioFadeOutCounter], a
ld a, [rNR50]
and a ; has the volume reached 0?
jr z, .fadeOutComplete
ld b, a
and $f
dec a
ld c, a
ld a, b
and $f0
swap a
dec a
swap a
or c
ld [rNR50], a
ret
.fadeOutComplete
ld a, [wAudioFadeOutControl]
ld b, a
xor a
ld [wAudioFadeOutControl], a
ld a, $ff
ld [wNewSoundID], a
call PlaySound
ld a, [wAudioSavedROMBank]
ld [wAudioROMBank], a
ld a, b
ld [wNewSoundID], a
jp PlaySound
; this function is used to display sign messages, sprite dialog, etc.
; INPUT: [hSpriteIndexOrTextID] = sprite ID or text ID
DisplayTextID::
ld a, [H_LOADEDROMBANK]
push af
callba DisplayTextIDInit ; initialization
ld hl, wTextPredefFlag
bit 0, [hl]
res 0, [hl]
jr nz, .skipSwitchToMapBank
ld a, [wCurMap]
call SwitchToMapRomBank
.skipSwitchToMapBank
ld a, 30 ; half a second
ld [H_FRAMECOUNTER], a ; used as joypad poll timer
ld hl, wMapTextPtr
ld a, [hli]
ld h, [hl]
ld l, a ; hl = map text pointer
ld d, $00
ld a, [hSpriteIndexOrTextID] ; text ID
ld [wSpriteIndex], a
and a
jp z, DisplayStartMenu
cp TEXT_SAFARI_GAME_OVER
jp z, DisplaySafariGameOverText
cp TEXT_MON_FAINTED
jp z, DisplayPokemonFaintedText
cp TEXT_BLACKED_OUT
jp z, DisplayPlayerBlackedOutText
cp TEXT_REPEL_WORE_OFF
jp z, DisplayRepelWoreOffText
ld a, [wNumSprites]
ld e, a
ld a, [hSpriteIndexOrTextID] ; sprite ID
cp e
jr z, .spriteHandling
jr nc, .skipSpriteHandling
.spriteHandling
; get the text ID of the sprite
push hl
push de
push bc
callba UpdateSpriteFacingOffsetAndDelayMovement ; update the graphics of the sprite the player is talking to (to face the right direction)
pop bc
pop de
ld hl, wMapSpriteData ; NPC text entries
ld a, [hSpriteIndexOrTextID]
dec a
add a
add l
ld l, a
jr nc, .noCarry
inc h
.noCarry
inc hl
ld a, [hl] ; a = text ID of the sprite
pop hl
.skipSpriteHandling
; look up the address of the text in the map's text entries
dec a
ld e, a
sla e
add hl, de
ld a, [hli]
ld h, [hl]
ld l, a ; hl = address of the text
ld a, [hl] ; a = first byte of text
; check first byte of text for special cases
cp $fe ; Pokemart NPC
jp z, DisplayPokemartDialogue
cp $ff ; Pokemon Center NPC
jp z, DisplayPokemonCenterDialogue
cp $fc ; Item Storage PC
jp z, FuncTX_ItemStoragePC
cp $fd ; Bill's PC
jp z, FuncTX_BillsPC
cp $f9 ; Pokemon Center PC
jp z, FuncTX_PokemonCenterPC
cp $f5 ; Vending Machine
jr nz, .notVendingMachine
callba VendingMachineMenu ; jump banks to vending machine routine
jr AfterDisplayingTextID
.notVendingMachine
cp $f7 ; prize menu
jp z, FuncTX_GameCornerPrizeMenu
cp $f6 ; cable connection NPC in Pokemon Center
jr nz, .notSpecialCase
callab CableClubNPC
jr AfterDisplayingTextID
.notSpecialCase
call PrintText_NoCreatingTextBox ; display the text
ld a, [wDoNotWaitForButtonPressAfterDisplayingText]
and a
jr nz, HoldTextDisplayOpen
AfterDisplayingTextID::
ld a, [wEnteringCableClub]
and a
jr nz, HoldTextDisplayOpen
call WaitForTextScrollButtonPress ; wait for a button press after displaying all the text
; loop to hold the dialogue box open as long as the player keeps holding down the A button
HoldTextDisplayOpen::
call Joypad
ld a, [hJoyHeld]
bit 0, a ; is the A button being pressed?
jr nz, HoldTextDisplayOpen
CloseTextDisplay::
ld a, [wCurMap]
call SwitchToMapRomBank
ld a, $90
ld [hWY], a ; move the window off the screen
call DelayFrame
call LoadGBPal
xor a
ld [H_AUTOBGTRANSFERENABLED], a ; disable continuous WRAM to VRAM transfer each V-blank
; loop to make sprites face the directions they originally faced before the dialogue
ld hl, wSpriteStateData2 + $19
ld c, $0f
ld de, $0010
.restoreSpriteFacingDirectionLoop
ld a, [hl]
dec h
ld [hl], a
inc h
add hl, de
dec c
jr nz, .restoreSpriteFacingDirectionLoop
ld a, BANK(InitMapSprites)
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
call InitMapSprites ; reload sprite tile pattern data (since it was partially overwritten by text tile patterns)
ld hl, wFontLoaded
res 0, [hl]
ld a, [wd732]
bit 3, a ; used fly warp
call z, LoadPlayerSpriteGraphics
call LoadCurrentMapView
pop af
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
jp UpdateSprites
DisplayPokemartDialogue::
push hl
ld hl, PokemartGreetingText
call PrintText
pop hl
inc hl
call LoadItemList
ld a, PRICEDITEMLISTMENU
ld [wListMenuID], a
ld a, [H_LOADEDROMBANK]
push af
ld a, Bank(DisplayPokemartDialogue_)
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
call DisplayPokemartDialogue_
pop af
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
jp AfterDisplayingTextID
PokemartGreetingText::
TX_FAR _PokemartGreetingText
db "@"
LoadItemList::
ld a, 1
ld [wUpdateSpritesEnabled], a
ld a, h
ld [wItemListPointer], a
ld a, l
ld [wItemListPointer + 1], a
ld de, wItemList
.loop
ld a, [hli]
ld [de], a
inc de
cp $ff
jr nz, .loop
ret
DisplayPokemonCenterDialogue::
; zeroing these doesn't appear to serve any purpose
xor a
ld [$ff8b], a
ld [$ff8c], a
ld [$ff8d], a
inc hl
ld a, [H_LOADEDROMBANK]
push af
ld a, Bank(DisplayPokemonCenterDialogue_)
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
call DisplayPokemonCenterDialogue_
pop af
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
jp AfterDisplayingTextID
DisplaySafariGameOverText::
callab PrintSafariGameOverText
jp AfterDisplayingTextID
DisplayPokemonFaintedText::
ld hl, PokemonFaintedText
call PrintText
jp AfterDisplayingTextID
PokemonFaintedText::
TX_FAR _PokemonFaintedText
db "@"
DisplayPlayerBlackedOutText::
ld hl, PlayerBlackedOutText
call PrintText
ld a, [wd732]
res 5, a ; reset forced to use bike bit
ld [wd732], a
jp HoldTextDisplayOpen
PlayerBlackedOutText::
TX_FAR _PlayerBlackedOutText
db "@"
DisplayRepelWoreOffText::
ld hl, RepelWoreOffText
call PrintText
jp AfterDisplayingTextID
RepelWoreOffText::
TX_FAR _RepelWoreOffText
db "@"
INCLUDE "engine/menu/start_menu.asm"
; function to count how many bits are set in a string of bytes
; INPUT:
; hl = address of string of bytes
; b = length of string of bytes
; OUTPUT:
; [wNumSetBits] = number of set bits
CountSetBits::
ld c, 0
.loop
ld a, [hli]
ld e, a
ld d, 8
.innerLoop ; count how many bits are set in the current byte
srl e
ld a, 0
adc c
ld c, a
dec d
jr nz, .innerLoop
dec b
jr nz, .loop
ld a, c
ld [wNumSetBits], a
ret
; subtracts the amount the player paid from their money
; sets carry flag if there is enough money and unsets carry flag if not
SubtractAmountPaidFromMoney::
jpba SubtractAmountPaidFromMoney_
; adds the amount the player sold to their money
AddAmountSoldToMoney::
ld de, wPlayerMoney + 2
ld hl, $ffa1 ; total price of items
ld c, 3 ; length of money in bytes
predef AddBCDPredef ; add total price to money
ld a, MONEY_BOX
ld [wTextBoxID], a
call DisplayTextBoxID ; redraw money text box
ld a, SFX_PURCHASE
call PlaySoundWaitForCurrent
jp WaitForSoundToFinish
; function to remove an item (in varying quantities) from the player's bag or PC box
; INPUT:
; HL = address of inventory (either wNumBagItems or wNumBoxItems)
; [wWhichPokemon] = index (within the inventory) of the item to remove
; [wItemQuantity] = quantity to remove
RemoveItemFromInventory::
ld a, [H_LOADEDROMBANK]
push af
ld a, BANK(RemoveItemFromInventory_)
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
call RemoveItemFromInventory_
pop af
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
ret
; function to add an item (in varying quantities) to the player's bag or PC box
; INPUT:
; HL = address of inventory (either wNumBagItems or wNumBoxItems)
; [wcf91] = item ID
; [wItemQuantity] = item quantity
; sets carry flag if successful, unsets carry flag if unsuccessful
AddItemToInventory::
push bc
ld a, [H_LOADEDROMBANK]
push af
ld a, BANK(AddItemToInventory_)
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
call AddItemToInventory_
pop bc
ld a, b
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
pop bc
ret
; INPUT:
; [wListMenuID] = list menu ID
; [wListPointer] = address of the list (2 bytes)
DisplayListMenuID::
xor a
ld [H_AUTOBGTRANSFERENABLED], a ; disable auto-transfer
ld a, 1
ld [hJoy7], a ; joypad state update flag
ld a, [wBattleType]
and a ; is it the Old Man battle?
jr nz, .specialBattleType
ld a, $01 ; hardcoded bank
jr .bankswitch
.specialBattleType ; Old Man battle
ld a, BANK(DisplayBattleMenu)
.bankswitch
call BankswitchHome
ld hl, wd730
set 6, [hl] ; turn off letter printing delay
xor a
ld [wMenuItemToSwap], a ; 0 means no item is currently being swapped
ld [wListCount], a
ld a, [wListPointer]
ld l, a
ld a, [wListPointer + 1]
ld h, a ; hl = address of the list
ld a, [hl] ; the first byte is the number of entries in the list
ld [wListCount], a
ld a, LIST_MENU_BOX
ld [wTextBoxID], a
call DisplayTextBoxID ; draw the menu text box
call UpdateSprites ; disable sprites behind the text box
; the code up to .skipMovingSprites appears to be useless
coord hl, 4, 2 ; coordinates of upper left corner of menu text box
lb de, 9, 14 ; height and width of menu text box
ld a, [wListMenuID]
and a ; is it a PC pokemon list?
jr nz, .skipMovingSprites
call UpdateSprites
.skipMovingSprites
ld a, 1 ; max menu item ID is 1 if the list has less than 2 entries
ld [wMenuWatchMovingOutOfBounds], a
ld a, [wListCount]
cp 2 ; does the list have less than 2 entries?
jr c, .setMenuVariables
ld a, 2 ; max menu item ID is 2 if the list has at least 2 entries
.setMenuVariables
ld [wMaxMenuItem], a
ld a, 4
ld [wTopMenuItemY], a
ld a, 5
ld [wTopMenuItemX], a
ld a, A_BUTTON | B_BUTTON | SELECT
ld [wMenuWatchedKeys], a
ld c, 10
call DelayFrames
DisplayListMenuIDLoop::
xor a
ld [H_AUTOBGTRANSFERENABLED], a ; disable transfer
call PrintListMenuEntries
ld a, 1
ld [H_AUTOBGTRANSFERENABLED], a ; enable transfer
call Delay3
ld a, [wBattleType]
and a ; is it the Old Man battle?
jr z, .notOldManBattle
.oldManBattle
ld a, "▶"
Coorda 5, 4 ; place menu cursor in front of first menu entry
ld c, 80
call DelayFrames
xor a
ld [wCurrentMenuItem], a
coord hl, 5, 4
ld a, l
ld [wMenuCursorLocation], a
ld a, h
ld [wMenuCursorLocation + 1], a
jr .buttonAPressed
.notOldManBattle
call LoadGBPal
call HandleMenuInput
push af
call PlaceMenuCursor
pop af
bit 0, a ; was the A button pressed?
jp z, .checkOtherKeys
.buttonAPressed
ld a, [wCurrentMenuItem]
call PlaceUnfilledArrowMenuCursor
; pointless because both values are overwritten before they are read
ld a, $01
ld [wMenuExitMethod], a
ld [wChosenMenuItem], a
xor a
ld [wMenuWatchMovingOutOfBounds], a
ld a, [wCurrentMenuItem]
ld c, a
ld a, [wListScrollOffset]
add c
ld c, a
ld a, [wListCount]
and a ; is the list empty?
jp z, ExitListMenu ; if so, exit the menu
dec a
cp c ; did the player select Cancel?
jp c, ExitListMenu ; if so, exit the menu
ld a, c
ld [wWhichPokemon], a
ld a, [wListMenuID]
cp ITEMLISTMENU
jr nz, .skipMultiplying
; if it's an item menu
sla c ; item entries are 2 bytes long, so multiply by 2
.skipMultiplying
ld a, [wListPointer]
ld l, a
ld a, [wListPointer + 1]
ld h, a
inc hl ; hl = beginning of list entries
ld b, 0
add hl, bc
ld a, [hl]
ld [wcf91], a
ld a, [wListMenuID]
and a ; is it a PC pokemon list?
jr z, .pokemonList
push hl
call GetItemPrice
pop hl
ld a, [wListMenuID]
cp ITEMLISTMENU
jr nz, .skipGettingQuantity
; if it's an item menu
inc hl
ld a, [hl] ; a = item quantity
ld [wMaxItemQuantity], a
.skipGettingQuantity
ld a, [wcf91]
ld [wd0b5], a
ld a, BANK(ItemNames)
ld [wPredefBank], a
call GetName
jr .storeChosenEntry
.pokemonList
ld hl, wPartyCount
ld a, [wListPointer]
cp l ; is it a list of party pokemon or box pokemon?
ld hl, wPartyMonNicks
jr z, .getPokemonName
ld hl, wBoxMonNicks ; box pokemon names
.getPokemonName
ld a, [wWhichPokemon]
call GetPartyMonName
.storeChosenEntry ; store the menu entry that the player chose and return
ld de, wcd6d
call CopyStringToCF4B ; copy name to wcf4b
ld a, CHOSE_MENU_ITEM
ld [wMenuExitMethod], a
ld a, [wCurrentMenuItem]
ld [wChosenMenuItem], a
xor a
ld [hJoy7], a ; joypad state update flag
ld hl, wd730
res 6, [hl] ; turn on letter printing delay
jp BankswitchBack
.checkOtherKeys ; check B, SELECT, Up, and Down keys
bit 1, a ; was the B button pressed?
jp nz, ExitListMenu ; if so, exit the menu
bit 2, a ; was the select button pressed?
jp nz, HandleItemListSwapping ; if so, allow the player to swap menu entries
ld b, a
bit 7, b ; was Down pressed?
ld hl, wListScrollOffset
jr z, .upPressed
.downPressed
ld a, [hl]
add 3
ld b, a
ld a, [wListCount]
cp b ; will going down scroll past the Cancel button?
jp c, DisplayListMenuIDLoop
inc [hl] ; if not, go down
jp DisplayListMenuIDLoop
.upPressed
ld a, [hl]
and a
jp z, DisplayListMenuIDLoop
dec [hl]
jp DisplayListMenuIDLoop
DisplayChooseQuantityMenu::
; text box dimensions/coordinates for just quantity
coord hl, 15, 9
ld b, 1 ; height
ld c, 3 ; width
ld a, [wListMenuID]
cp PRICEDITEMLISTMENU
jr nz, .drawTextBox
; text box dimensions/coordinates for quantity and price
coord hl, 7, 9
ld b, 1 ; height
ld c, 11 ; width
.drawTextBox
call TextBoxBorder
coord hl, 16, 10
ld a, [wListMenuID]
cp PRICEDITEMLISTMENU
jr nz, .printInitialQuantity
coord hl, 8, 10
.printInitialQuantity
ld de, InitialQuantityText
call PlaceString
xor a
ld [wItemQuantity], a ; initialize current quantity to 0
jp .incrementQuantity
.waitForKeyPressLoop
call JoypadLowSensitivity
ld a, [hJoyPressed] ; newly pressed buttons
bit 0, a ; was the A button pressed?
jp nz, .buttonAPressed
bit 1, a ; was the B button pressed?
jp nz, .buttonBPressed
bit 6, a ; was Up pressed?
jr nz, .incrementQuantity
bit 7, a ; was Down pressed?
jr nz, .decrementQuantity
jr .waitForKeyPressLoop
.incrementQuantity
ld a, [wMaxItemQuantity]
inc a
ld b, a
ld hl, wItemQuantity ; current quantity
inc [hl]
ld a, [hl]
cp b
jr nz, .handleNewQuantity
; wrap to 1 if the player goes above the max quantity
ld a, 1
ld [hl], a
jr .handleNewQuantity
.decrementQuantity
ld hl, wItemQuantity ; current quantity
dec [hl]
jr nz, .handleNewQuantity
; wrap to the max quantity if the player goes below 1
ld a, [wMaxItemQuantity]
ld [hl], a
.handleNewQuantity
coord hl, 17, 10
ld a, [wListMenuID]
cp PRICEDITEMLISTMENU
jr nz, .printQuantity
.printPrice
ld c, $03
ld a, [wItemQuantity]
ld b, a
ld hl, hMoney ; total price
; initialize total price to 0
xor a
ld [hli], a
ld [hli], a
ld [hl], a
.addLoop ; loop to multiply the individual price by the quantity to get the total price
ld de, hMoney + 2
ld hl, hItemPrice + 2
push bc
predef AddBCDPredef ; add the individual price to the current sum
pop bc
dec b
jr nz, .addLoop
ld a, [hHalveItemPrices]
and a ; should the price be halved (for selling items)?
jr z, .skipHalvingPrice
xor a
ld [hDivideBCDDivisor], a
ld [hDivideBCDDivisor + 1], a
ld a, $02
ld [hDivideBCDDivisor + 2], a
predef DivideBCDPredef3 ; halves the price
; store the halved price
ld a, [hDivideBCDQuotient]
ld [hMoney], a
ld a, [hDivideBCDQuotient + 1]
ld [hMoney + 1], a
ld a, [hDivideBCDQuotient + 2]
ld [hMoney + 2], a
.skipHalvingPrice
coord hl, 12, 10
ld de, SpacesBetweenQuantityAndPriceText
call PlaceString
ld de, hMoney ; total price
ld c, $a3
call PrintBCDNumber
coord hl, 9, 10
.printQuantity
ld de, wItemQuantity ; current quantity
lb bc, LEADING_ZEROES | 1, 2 ; 1 byte, 2 digits
call PrintNumber
jp .waitForKeyPressLoop
.buttonAPressed ; the player chose to make the transaction
xor a
ld [wMenuItemToSwap], a ; 0 means no item is currently being swapped
ret
.buttonBPressed ; the player chose to cancel the transaction
xor a
ld [wMenuItemToSwap], a ; 0 means no item is currently being swapped
ld a, $ff
ret
InitialQuantityText::
db "×01@"
SpacesBetweenQuantityAndPriceText::
db " @"
ExitListMenu::
ld a, [wCurrentMenuItem]
ld [wChosenMenuItem], a
ld a, CANCELLED_MENU
ld [wMenuExitMethod], a
ld [wMenuWatchMovingOutOfBounds], a
xor a
ld [hJoy7], a
ld hl, wd730
res 6, [hl]
call BankswitchBack
xor a
ld [wMenuItemToSwap], a ; 0 means no item is currently being swapped
scf
ret
PrintListMenuEntries::
coord hl, 5, 3
ld b, 9
ld c, 14
call ClearScreenArea
ld a, [wListPointer]
ld e, a
ld a, [wListPointer + 1]
ld d, a
inc de ; de = beginning of list entries
ld a, [wListScrollOffset]
ld c, a
ld a, [wListMenuID]
cp ITEMLISTMENU
ld a, c
jr nz, .skipMultiplying
; if it's an item menu
; item entries are 2 bytes long, so multiply by 2
sla a
sla c
.skipMultiplying
add e
ld e, a
jr nc, .noCarry
inc d
.noCarry
coord hl, 6, 4 ; coordinates of first list entry name
ld b, 4 ; print 4 names
.loop
ld a, b
ld [wWhichPokemon], a
ld a, [de]
ld [wd11e], a
cp $ff
jp z, .printCancelMenuItem
push bc
push de
push hl
push hl
push de
ld a, [wListMenuID]
and a
jr z, .pokemonPCMenu
cp MOVESLISTMENU
jr z, .movesMenu
.itemMenu
call GetItemName
jr .placeNameString
.pokemonPCMenu
push hl
ld hl, wPartyCount
ld a, [wListPointer]
cp l ; is it a list of party pokemon or box pokemon?
ld hl, wPartyMonNicks
jr z, .getPokemonName
ld hl, wBoxMonNicks ; box pokemon names
.getPokemonName
ld a, [wWhichPokemon]
ld b, a
ld a, 4
sub b
ld b, a
ld a, [wListScrollOffset]
add b
call GetPartyMonName
pop hl
jr .placeNameString
.movesMenu
call GetMoveName
.placeNameString
call PlaceString
pop de
pop hl
ld a, [wPrintItemPrices]
and a ; should prices be printed?
jr z, .skipPrintingItemPrice
.printItemPrice
push hl
ld a, [de]
ld de, ItemPrices
ld [wcf91], a
call GetItemPrice ; get price
pop hl
ld bc, SCREEN_WIDTH + 5 ; 1 row down and 5 columns right
add hl, bc
ld c, $a3 ; no leading zeroes, right-aligned, print currency symbol, 3 bytes
call PrintBCDNumber
.skipPrintingItemPrice
ld a, [wListMenuID]
and a
jr nz, .skipPrintingPokemonLevel
.printPokemonLevel
ld a, [wd11e]
push af
push hl
ld hl, wPartyCount
ld a, [wListPointer]
cp l ; is it a list of party pokemon or box pokemon?
ld a, PLAYER_PARTY_DATA
jr z, .next
ld a, BOX_DATA
.next
ld [wMonDataLocation], a
ld hl, wWhichPokemon
ld a, [hl]
ld b, a
ld a, $04
sub b
ld b, a
ld a, [wListScrollOffset]
add b
ld [hl], a
call LoadMonData
ld a, [wMonDataLocation]
and a ; is it a list of party pokemon or box pokemon?
jr z, .skipCopyingLevel
.copyLevel
ld a, [wLoadedMonBoxLevel]
ld [wLoadedMonLevel], a
.skipCopyingLevel
pop hl
ld bc, $001c
add hl, bc
call PrintLevel
pop af
ld [wd11e], a
.skipPrintingPokemonLevel
pop hl
pop de
inc de
ld a, [wListMenuID]
cp ITEMLISTMENU
jr nz, .nextListEntry
.printItemQuantity
ld a, [wd11e]
ld [wcf91], a
call IsKeyItem ; check if item is unsellable
ld a, [wIsKeyItem]
and a ; is the item unsellable?
jr nz, .skipPrintingItemQuantity ; if so, don't print the quantity
push hl
ld bc, SCREEN_WIDTH + 8 ; 1 row down and 8 columns right
add hl, bc
ld a, "×"
ld [hli], a
ld a, [wd11e]
push af
ld a, [de]
ld [wMaxItemQuantity], a
push de
ld de, wd11e
ld [de], a
lb bc, 1, 2
call PrintNumber
pop de
pop af
ld [wd11e], a
pop hl
.skipPrintingItemQuantity
inc de
pop bc
inc c
push bc
inc c
ld a, [wMenuItemToSwap] ; ID of item chosen for swapping (counts from 1)
and a ; is an item being swapped?
jr z, .nextListEntry
sla a
cp c ; is it this item?
jr nz, .nextListEntry
dec hl
ld a, $ec ; unfilled right arrow menu cursor to indicate an item being swapped
ld [hli], a
.nextListEntry
ld bc, 2 * SCREEN_WIDTH ; 2 rows
add hl, bc
pop bc
inc c
dec b
jp nz, .loop
ld bc, -8
add hl, bc
ld a, "▼"
ld [hl], a
ret
.printCancelMenuItem
ld de, ListMenuCancelText
jp PlaceString
ListMenuCancelText::
db "CANCEL@"
GetMonName::
push hl
ld a, [H_LOADEDROMBANK]
push af
ld a, BANK(MonsterNames)
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
ld a, [wd11e]
dec a
ld hl, MonsterNames
ld c, 10
ld b, 0
call AddNTimes
ld de, wcd6d
push de
ld bc, 10
call CopyData
ld hl, wcd6d + 10
ld [hl], "@"
pop de
pop af
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
pop hl
ret
GetItemName::
; given an item ID at [wd11e], store the name of the item into a string
; starting at wcd6d
push hl
push bc
ld a, [wd11e]
cp HM_01 ; is this a TM/HM?
jr nc, .Machine
ld [wd0b5], a
ld a, ITEM_NAME
ld [wNameListType], a
ld a, BANK(ItemNames)
ld [wPredefBank], a
call GetName
jr .Finish
.Machine
call GetMachineName
.Finish
ld de, wcd6d ; pointer to where item name is stored in RAM
pop bc
pop hl
ret
GetMachineName::
; copies the name of the TM/HM in [wd11e] to wcd6d
push hl
push de
push bc
ld a, [wd11e]
push af
cp TM_01 ; is this a TM? [not HM]
jr nc, .WriteTM
; if HM, then write "HM" and add 5 to the item ID, so we can reuse the
; TM printing code
add 5
ld [wd11e], a
ld hl, HiddenPrefix ; points to "HM"
ld bc, 2
jr .WriteMachinePrefix
.WriteTM
ld hl, TechnicalPrefix ; points to "TM"
ld bc, 2
.WriteMachinePrefix
ld de, wcd6d
call CopyData
; now get the machine number and convert it to text
ld a, [wd11e]
sub TM_01 - 1
ld b, "0"
.FirstDigit
sub 10
jr c, .SecondDigit
inc b
jr .FirstDigit
.SecondDigit
add 10
push af
ld a, b
ld [de], a
inc de
pop af
ld b, "0"
add b
ld [de], a
inc de
ld a, "@"
ld [de], a
pop af
ld [wd11e], a
pop bc
pop de
pop hl
ret
TechnicalPrefix::
db "TM"
HiddenPrefix::
db "HM"
; sets carry if item is HM, clears carry if item is not HM
; Input: a = item ID
IsItemHM::
cp HM_01
jr c, .notHM
cp TM_01
ret
.notHM
and a
ret
; sets carry if move is an HM, clears carry if move is not an HM
; Input: a = move ID
IsMoveHM::
ld hl, HMMoves
ld de, 1
jp IsInArray
HMMoves::
db CUT,FLY,SURF,STRENGTH,FLASH
db $ff ; terminator
GetMoveName::
push hl
ld a, MOVE_NAME
ld [wNameListType], a
ld a, [wd11e]
ld [wd0b5], a
ld a, BANK(MoveNames)
ld [wPredefBank], a
call GetName
ld de, wcd6d ; pointer to where move name is stored in RAM
pop hl
ret
; reloads text box tile patterns, current map view, and tileset tile patterns
ReloadMapData::
ld a, [H_LOADEDROMBANK]
push af
ld a, [wCurMap]
call SwitchToMapRomBank
call DisableLCD
call LoadTextBoxTilePatterns
call LoadCurrentMapView
call LoadTilesetTilePatternData
call EnableLCD
pop af
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
ret
; reloads tileset tile patterns
ReloadTilesetTilePatterns::
ld a, [H_LOADEDROMBANK]
push af
ld a, [wCurMap]
call SwitchToMapRomBank
call DisableLCD
call LoadTilesetTilePatternData
call EnableLCD
pop af
ld [H_LOADEDROMBANK], a
ld [MBC1RomBank], a
ret
; shows the town map and lets the player choose a destination to fly to
ChooseFlyDestination::
ld hl, wd72e
res 4, [hl]
jpba LoadTownMap_Fly
; causes the text box to close without waiting for a button press after displaying text
DisableWaitingAfterTextDisplay::
ld a, $01
ld [wDoNotWaitForButtonPressAfterDisplayingText], a
ret
; uses an item
; UseItem is used with dummy items to perform certain other functions as well
; INPUT:
; [wcf91] = item ID
; OUTPUT:
; [wActionResultOrTookBattleTurn] = success
; 00: 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, 14, 7
ld bc, $80f
ret
YesNoChoicePokeCenter::
call SaveScreenTilesToBuffer1
ld a, HEAL_CANCEL_MENU
ld [wTwoOptionMenuID], a
coord hl, 11, 6
lb bc, 8, 12
jr DisplayYesNoChoice
WideYesNoChoice:: ; unused
call SaveScreenTilesToBuffer1
ld a, WIDE_YES_NO_MENU
ld [wTwoOptionMenuID], a
coord hl, 12, 7
lb bc, 8, 13
DisplayYesNoChoice::
ld a, TWO_OPTION_MENU
ld [wTextBoxID], a
call DisplayTextBoxID
jp LoadScreenTilesFromBuffer1
; calculates the difference |a-b|, setting carry flag if 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 [wcf4b]
CopyStringToCF4B::
ld hl, wcf4b
; 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 "▶" ; 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 wcf4b.
; Return carry on success.
ld a, b
ld [wd11e], a
ld [wcf91], a
ld a, c
ld [wItemQuantity], a
ld hl, wNumBagItems
call AddItemToInventory
ret nc
call GetItemName
call CopyStringToCF4B
scf
ret
GivePokemon::
; Give the player monster b at level c.
ld a, b
ld [wcf91], a
ld a, c
ld [wCurEnemyLVL], a
xor a ; PLAYER_PARTY_DATA
ld [wMonDataLocation], a
jpba _GivePokemon
Random::
; Return a random number in a.
; For battles, use BattleRandom.
push hl
push de
push bc
callba Random_
ld a, [hRandomAdd]
pop bc
pop de
pop hl
ret
INCLUDE "home/predef.asm"
UpdateCinnabarGymGateTileBlocks::
jpba UpdateCinnabarGymGateTileBlocks_
CheckForHiddenObjectOrBookshelfOrCardKeyDoor::
ld a, [H_LOADEDROMBANK]
push af
ld a, [hJoyHeld]
bit 0, a ; A button
jr z, .nothingFound
; A button is pressed
ld a, Bank(CheckForHiddenObject)
ld [MBC1RomBank], a
ld [H_LOADEDROMBANK], a
call CheckForHiddenObject
ld a, [$ffee]
and a
jr nz, .hiddenObjectNotFound
ld a, [wHiddenObjectFunctionRomBank]
ld [MBC1RomBank], a
ld [H_LOADEDROMBANK], a
ld de, .returnAddress
push de
jp hl
.returnAddress
xor a
jr .done
.hiddenObjectNotFound
callba PrintBookshelfText
ld a, [$ffdb]
and a
jr z, .done
.nothingFound
ld a, $ff
.done
ld [$ffeb], a
pop af
ld [MBC1RomBank], a
ld [H_LOADEDROMBANK], a
ret
PrintPredefTextID::
ld [hSpriteIndexOrTextID], a
ld hl, TextPredefs
call SetMapTextPointer
ld hl, wTextPredefFlag
set 0, [hl]
call DisplayTextID
RestoreMapTextPointer::
ld hl, wMapTextPtr
ld a, [$ffec]
ld [hli], a
ld a, [$ffec + 1]
ld [hl], a
ret
SetMapTextPointer::
ld a, [wMapTextPtr]
ld [$ffec], a
ld a, [wMapTextPtr + 1]
ld [$ffec + 1], a
ld a, l
ld [wMapTextPtr], a
ld a, h
ld [wMapTextPtr + 1], a
ret
TextPredefs::
const_value = 1
add_tx_pre CardKeySuccessText ; 01
add_tx_pre CardKeyFailText ; 02
add_tx_pre RedBedroomPCText ; 03
add_tx_pre RedBedroomSNESText ; 04
add_tx_pre PushStartText ; 05
add_tx_pre SaveOptionText ; 06
add_tx_pre StrengthsAndWeaknessesText ; 07
add_tx_pre OakLabEmailText ; 08
add_tx_pre AerodactylFossilText ; 09
add_tx_pre Route15UpstairsBinocularsText ; 0A
add_tx_pre KabutopsFossilText ; 0B
add_tx_pre GymStatueText1 ; 0C
add_tx_pre GymStatueText2 ; 0D
add_tx_pre BookcaseText ; 0E
add_tx_pre ViridianCityPokecenterBenchGuyText ; 0F
add_tx_pre PewterCityPokecenterBenchGuyText ; 10
add_tx_pre CeruleanCityPokecenterBenchGuyText ; 11
add_tx_pre LavenderCityPokecenterBenchGuyText ; 12
add_tx_pre VermilionCityPokecenterBenchGuyText ; 13
add_tx_pre CeladonCityPokecenterBenchGuyText ; 14
add_tx_pre CeladonCityHotelText ; 15
add_tx_pre FuchsiaCityPokecenterBenchGuyText ; 16
add_tx_pre CinnabarIslandPokecenterBenchGuyText ; 17
add_tx_pre SaffronCityPokecenterBenchGuyText ; 18
add_tx_pre MtMoonPokecenterBenchGuyText ; 19
add_tx_pre RockTunnelPokecenterBenchGuyText ; 1A
add_tx_pre UnusedBenchGuyText1 ; 1B XXX unused
add_tx_pre UnusedBenchGuyText2 ; 1C XXX unused
add_tx_pre UnusedBenchGuyText3 ; 1D XXX unused
add_tx_pre UnusedPredefText ; 1E XXX unused
add_tx_pre PokemonCenterPCText ; 1F
add_tx_pre ViridianSchoolNotebook ; 20
add_tx_pre ViridianSchoolBlackboard ; 21
add_tx_pre JustAMomentText ; 22
add_tx_pre OpenBillsPCText ; 23
add_tx_pre FoundHiddenItemText ; 24
add_tx_pre HiddenItemBagFullText ; 25 XXX unused
add_tx_pre VermilionGymTrashText ; 26
add_tx_pre IndigoPlateauHQText ; 27
add_tx_pre GameCornerOutOfOrderText ; 28
add_tx_pre GameCornerOutToLunchText ; 29
add_tx_pre GameCornerSomeonesKeysText ; 2A
add_tx_pre FoundHiddenCoinsText ; 2B
add_tx_pre DroppedHiddenCoinsText ; 2C
add_tx_pre BillsHouseMonitorText ; 2D
add_tx_pre BillsHouseInitiatedText ; 2E
add_tx_pre BillsHousePokemonList ; 2F
add_tx_pre MagazinesText ; 30
add_tx_pre CinnabarGymQuiz ; 31
add_tx_pre GameCornerNoCoinsText ; 32
add_tx_pre GameCornerCoinCaseText ; 33
add_tx_pre LinkCableHelp ; 34
add_tx_pre TMNotebook ; 35
add_tx_pre FightingDojoText ; 36
add_tx_pre EnemiesOnEverySideText ; 37
add_tx_pre WhatGoesAroundComesAroundText ; 38
add_tx_pre NewBicycleText ; 39
add_tx_pre IndigoPlateauStatues ; 3A
add_tx_pre VermilionGymTrashSuccessText1 ; 3B
add_tx_pre VermilionGymTrashSuccessText2 ; 3C XXX unused
add_tx_pre VermilionGymTrashSuccessText3 ; 3D
add_tx_pre VermilionGymTrashFailText ; 3E
add_tx_pre TownMapText ; 3F
add_tx_pre BookOrSculptureText ; 40
add_tx_pre ElevatorText ; 41
add_tx_pre PokemonStuffText ; 42