First Commit

Upload literally everything from the pokecrystal16 expand-move-ID branch
This commit is contained in:
Zeta_Null 2023-09-10 12:35:35 -04:00
commit 2f8a41f833
4618 changed files with 480386 additions and 0 deletions

92
home/16bit.asm Normal file
View file

@ -0,0 +1,92 @@
MACRO ___conversion_table_homecall
; macro arguments: homecall type, function label
; all functions clobber af and hl (except for outputs) and preserve bc and de
; homecall types:
; - read: index to ID conversion (in: a = 8-bit ID; out: hl = 16-bit index)
; - write: ID to index conversion (in: hl = 16-bit index; out: a = 8-bit ID)
; - lock: ID locking (in: a = ID (or zero to unlock), l = position; out: a = preserved)
if strcmp("\1", "read") && strcmp("\1", "write") && strcmp("\1", "lock")
fail "16-bit homecall: invalid call type"
endc
if (strcmp("\1", "write") != 0)
ld h, a
endc
ldh a, [hROMBank]
push af
ld a, BANK(\2)
rst Bankswitch
if (strcmp("\1", "read") == 0)
ld a, h
endc
call \2
if (strcmp("\1", "read") != 0)
ld l, a
endc
pop af
rst Bankswitch
if (strcmp("\1", "read") != 0)
ld a, l
endc
ret
ENDM
MACRO ___conversion_table_homecall_readlocked
; macro argument: table name
; in: a = position
; out: a = 8-bit index; everything else preserved
push hl
add a, LOW(\1LockedEntries)
ld l, a
ldh a, [rSVBK]
ld h, a
ld a, BANK(\1LockedEntries)
ldh [rSVBK], a
ld a, h
ld h, HIGH(\1LockedEntries)
ld l, [hl]
ldh [rSVBK], a
ld a, l
pop hl
ret
ENDM
; in: a = 8-bit index
; out: hl = 16-bit index; a clobbered
GetPokemonIndexFromID::
___conversion_table_homecall read, _GetPokemonIndexFromID
; in: hl = 16-bit index
; out: a = 8-bit index, hl clobbered
GetPokemonIDFromIndex::
___conversion_table_homecall write, _GetPokemonIDFromIndex
; in: a = 8-bit index or zero (to clear), l = position
; out: a = unchanged, hl = clobbered
LockPokemonID::
___conversion_table_homecall lock, _LockPokemonID
; in: a = position
; out: a = 8-bit index; everything else preserved
GetLockedPokemonID::
___conversion_table_homecall_readlocked wPokemonIndexTable
; in: a = 8-bit index
; out: hl = 16-bit index; a clobbered
GetMoveIndexFromID::
___conversion_table_homecall read, _GetMoveIndexFromID
; in: hl = 16-bit index
; out: a = 8-bit index, hl clobbered
GetMoveIDFromIndex::
___conversion_table_homecall write, _GetMoveIDFromIndex
; in: a = 8-bit index or zero (to clear), l = position
; out: a = unchanged, hl = clobbered
LockMoveID::
___conversion_table_homecall lock, _LockMoveID
; in: a = position
; out: a = 8-bit index; everything else preserved
GetLockedMoveID::
___conversion_table_homecall_readlocked wMoveIndexTable

71
home/array.asm Normal file
View file

@ -0,0 +1,71 @@
IsInArray::
; Find value a for every de bytes in array hl.
; Return index in b and carry if found.
ld b, 0
ld c, a
.loop
ld a, [hl]
cp -1
jr z, .NotInArray
cp c
jr z, .InArray
inc b
add hl, de
jr .loop
.NotInArray:
and a
ret
.InArray:
scf
ret
IsInWordArray_NextItem:
add hl, de
IsInWordArray::
; Same as IsInArray, but for word values. The value is input in bc; index not returned.
ld a, [hli]
and [hl]
inc a
ret z
ld a, [hld]
cp b
jr nz, IsInWordArray_NextItem
ld a, [hl]
cp c
jr nz, IsInWordArray_NextItem
scf
ret
SkipNames::
; Skip a names.
ld bc, NAME_LENGTH
and a
ret z
.loop
add hl, bc
dec a
jr nz, .loop
ret
AddNTimes::
; Add bc * a to hl.
; Preserves bc
and a
ret z
push bc
.loop
rra ; and a from below and above resets carry
jr nc, .noadd
add hl, bc
.noadd
sla c
rl b
and a
jr nz, .loop
.done
pop bc
ret

517
home/audio.asm Normal file
View file

@ -0,0 +1,517 @@
; Audio interfaces.
InitSound::
push hl
push de
push bc
push af
ldh a, [hROMBank]
push af
ld a, BANK(_InitSound)
ldh [hROMBank], a
ld [MBC3RomBank], a
call _InitSound
pop af
ldh [hROMBank], a
ld [MBC3RomBank], a
pop af
pop bc
pop de
pop hl
ret
UpdateSound::
push hl
push de
push bc
push af
ldh a, [hROMBank]
push af
ld a, BANK(_UpdateSound)
ldh [hROMBank], a
ld [MBC3RomBank], a
call _UpdateSound
pop af
ldh [hROMBank], a
ld [MBC3RomBank], a
pop af
pop bc
pop de
pop hl
ret
_LoadMusicByte::
; [wCurMusicByte] = [a:de]
ldh [hROMBank], a
ld [MBC3RomBank], a
ld a, [de]
ld [wCurMusicByte], a
ld a, BANK(LoadMusicByte)
ldh [hROMBank], a
ld [MBC3RomBank], a
ret
PlayMusic::
; Play music de.
push hl
push de
push bc
push af
ldh a, [hROMBank]
push af
ld a, BANK(_PlayMusic) ; aka BANK(_InitSound)
ldh [hROMBank], a
ld [MBC3RomBank], a
ld a, e
and a
jr z, .nomusic
call _PlayMusic
jr .end
.nomusic
call _InitSound
.end
pop af
ldh [hROMBank], a
ld [MBC3RomBank], a
pop af
pop bc
pop de
pop hl
ret
PlayMusic2::
; Stop playing music, then play music de.
push hl
push de
push bc
push af
ldh a, [hROMBank]
push af
ld a, BANK(_PlayMusic)
ldh [hROMBank], a
ld [MBC3RomBank], a
push de
ld de, MUSIC_NONE
call _PlayMusic
call DelayFrame
pop de
call _PlayMusic
pop af
ldh [hROMBank], a
ld [MBC3RomBank], a
pop af
pop bc
pop de
pop hl
ret
PlayCry::
; Play cry de.
push hl
push de
push bc
push af
ldh a, [hROMBank]
push af
; Cries are stuck in one bank.
ld a, BANK(PokemonCries)
ldh [hROMBank], a
ld [MBC3RomBank], a
ld hl, PokemonCries
rept MON_CRY_LENGTH
add hl, de
endr
ld e, [hl]
inc hl
ld d, [hl]
inc hl
ld a, [hli]
ld [wCryPitch], a
ld a, [hli]
ld [wCryPitch + 1], a
ld a, [hli]
ld [wCryLength], a
ld a, [hl]
ld [wCryLength + 1], a
ld a, BANK(_PlayCry)
ldh [hROMBank], a
ld [MBC3RomBank], a
call _PlayCry
pop af
ldh [hROMBank], a
ld [MBC3RomBank], a
pop af
pop bc
pop de
pop hl
ret
PlaySFX::
; Play sound effect de.
; Sound effects are ordered by priority (highest to lowest)
push hl
push de
push bc
push af
; Is something already playing?
call CheckSFX
jr nc, .play
; Does it have priority?
ld a, [wCurSFX]
cp e
jr c, .done
.play
ldh a, [hROMBank]
push af
ld a, BANK(_PlaySFX)
ldh [hROMBank], a
ld [MBC3RomBank], a
ld a, e
ld [wCurSFX], a
call _PlaySFX
pop af
ldh [hROMBank], a
ld [MBC3RomBank], a
.done
pop af
pop bc
pop de
pop hl
ret
WaitPlaySFX::
call WaitSFX
jp PlaySFX
WaitSFX::
; infinite loop until sfx is done playing
push hl
.wait
ld hl, wChannel5Flags1
bit 0, [hl]
jr nz, .wait
ld hl, wChannel6Flags1
bit 0, [hl]
jr nz, .wait
ld hl, wChannel7Flags1
bit 0, [hl]
jr nz, .wait
ld hl, wChannel8Flags1
bit 0, [hl]
jr nz, .wait
pop hl
ret
IsSFXPlaying::
; Return carry if no sound effect is playing.
; The inverse of CheckSFX.
push hl
ld hl, wChannel5Flags1
bit 0, [hl]
jr nz, .playing
ld hl, wChannel6Flags1
bit 0, [hl]
jr nz, .playing
ld hl, wChannel7Flags1
bit 0, [hl]
jr nz, .playing
ld hl, wChannel8Flags1
bit 0, [hl]
jr nz, .playing
pop hl
scf
ret
.playing
pop hl
and a
ret
MaxVolume::
ld a, MAX_VOLUME
ld [wVolume], a
ret
LowVolume::
ld a, $33 ; 50%
ld [wVolume], a
ret
MinVolume::
xor a
ld [wVolume], a
ret
FadeOutToMusic:: ; unreferenced
ld a, 4
ld [wMusicFade], a
ret
FadeInToMusic::
ld a, 4 | (1 << MUSIC_FADE_IN_F)
ld [wMusicFade], a
ret
SkipMusic::
; Skip a frames of music.
.loop
and a
ret z
dec a
call UpdateSound
jr .loop
FadeToMapMusic::
push hl
push de
push bc
push af
call GetMapMusic_MaybeSpecial
ld a, [wMapMusic]
cp e
jr z, .done
ld a, 8
ld [wMusicFade], a
ld a, e
ld [wMusicFadeID], a
ld a, d
ld [wMusicFadeID + 1], a
ld a, e
ld [wMapMusic], a
.done
pop af
pop bc
pop de
pop hl
ret
PlayMapMusic::
push hl
push de
push bc
push af
call GetMapMusic_MaybeSpecial
ld a, [wMapMusic]
cp e
jr z, .done
push de
ld de, MUSIC_NONE
call PlayMusic
call DelayFrame
pop de
ld a, e
ld [wMapMusic], a
call PlayMusic
.done
pop af
pop bc
pop de
pop hl
ret
PlayMapMusicBike::
; If the player's on a bike, play the bike music instead of the map music
push hl
push de
push bc
push af
xor a
ld [wDontPlayMapMusicOnReload], a
ld de, MUSIC_BICYCLE
ld a, [wPlayerState]
cp PLAYER_BIKE
jr z, .play
call GetMapMusic_MaybeSpecial
.play
push de
ld de, MUSIC_NONE
call PlayMusic
call DelayFrame
pop de
ld a, e
ld [wMapMusic], a
call PlayMusic
pop af
pop bc
pop de
pop hl
ret
TryRestartMapMusic::
ld a, [wDontPlayMapMusicOnReload]
and a
jr z, RestartMapMusic
xor a
ld [wMapMusic], a
ld de, MUSIC_NONE
call PlayMusic
call DelayFrame
xor a
ld [wDontPlayMapMusicOnReload], a
ret
RestartMapMusic::
push hl
push de
push bc
push af
ld de, MUSIC_NONE
call PlayMusic
call DelayFrame
ld a, [wMapMusic]
ld e, a
ld d, 0
call PlayMusic
pop af
pop bc
pop de
pop hl
ret
SpecialMapMusic::
ld a, [wPlayerState]
cp PLAYER_SURF
jr z, .surf
cp PLAYER_SURF_PIKA
jr z, .surf
ld a, [wStatusFlags2]
bit STATUSFLAGS2_BUG_CONTEST_TIMER_F, a
jr nz, .contest
.no
and a
ret
.bike ; unreferenced
ld de, MUSIC_BICYCLE
scf
ret
.surf
ld de, MUSIC_SURF
scf
ret
.contest
ld a, [wMapGroup]
cp GROUP_ROUTE_35_NATIONAL_PARK_GATE
jr nz, .no
ld a, [wMapNumber]
cp MAP_ROUTE_35_NATIONAL_PARK_GATE
jr z, .ranking
cp MAP_ROUTE_36_NATIONAL_PARK_GATE
jr nz, .no
.ranking
ld de, MUSIC_BUG_CATCHING_CONTEST_RANKING
scf
ret
GetMapMusic_MaybeSpecial::
call SpecialMapMusic
ret c
jp GetMapMusic
CheckSFX::
; Return carry if any SFX channels are active.
ld a, [wChannel5Flags1]
bit 0, a
jr nz, .playing
ld a, [wChannel6Flags1]
bit 0, a
jr nz, .playing
ld a, [wChannel7Flags1]
bit 0, a
jr nz, .playing
ld a, [wChannel8Flags1]
bit 0, a
jr nz, .playing
and a
ret
.playing
scf
ret
TerminateExpBarSound::
xor a
ld [wChannel5Flags1], a
ld [wPitchSweep], a
ldh [rNR10], a
ldh [rNR11], a
ldh [rNR12], a
ldh [rNR13], a
ldh [rNR14], a
ret
ChannelsOff::
; Quickly turn off music channels
xor a
ld [wChannel1Flags1], a
ld [wChannel2Flags1], a
ld [wChannel3Flags1], a
ld [wChannel4Flags1], a
ld [wPitchSweep], a
ret
SFXChannelsOff::
; Quickly turn off sound effect channels
xor a
ld [wChannel5Flags1], a
ld [wChannel6Flags1], a
ld [wChannel7Flags1], a
ld [wChannel8Flags1], a
ld [wPitchSweep], a
ret

273
home/battle.asm Normal file
View file

@ -0,0 +1,273 @@
GetPartyParamLocation::
; Get the location of parameter a from wCurPartyMon in hl
push bc
ld hl, wPartyMons
ld c, a
ld b, 0
add hl, bc
ld a, [wCurPartyMon]
call GetPartyLocation
pop bc
ret
GetPartyLocation::
; Add the length of a PartyMon struct to hl a times.
ld bc, PARTYMON_STRUCT_LENGTH
jp AddNTimes
FarSkipEvolutions::
; Calls SkipEvolutions from another bank. It can't be a farcall because it uses hl.
ldh a, [hROMBank]
push af
ld a, BANK(SkipEvolutions)
rst Bankswitch
call SkipEvolutions
pop af
rst Bankswitch
ret
UserPartyAttr::
push af
ldh a, [hBattleTurn]
and a
jr nz, .ot
pop af
jr BattlePartyAttr
.ot
pop af
jr OTPartyAttr
OpponentPartyAttr::
push af
ldh a, [hBattleTurn]
and a
jr z, .ot
pop af
jr BattlePartyAttr
.ot
pop af
jr OTPartyAttr
BattlePartyAttr::
; Get attribute a from the party struct of the active battle mon.
push bc
ld c, a
ld b, 0
ld hl, wPartyMons
add hl, bc
ld a, [wCurBattleMon]
call GetPartyLocation
pop bc
ret
OTPartyAttr::
; Get attribute a from the party struct of the active enemy mon.
push bc
ld c, a
ld b, 0
ld hl, wOTPartyMon1Species
add hl, bc
ld a, [wCurOTMon]
call GetPartyLocation
pop bc
ret
ResetDamage::
xor a
ld [wCurDamage], a
ld [wCurDamage + 1], a
ret
SetPlayerTurn::
xor a
ldh [hBattleTurn], a
ret
SetEnemyTurn::
ld a, 1
ldh [hBattleTurn], a
ret
UpdateOpponentInParty::
ldh a, [hBattleTurn]
and a
jr z, UpdateEnemyMonInParty
jr UpdateBattleMonInParty
UpdateUserInParty::
ldh a, [hBattleTurn]
and a
jr z, UpdateBattleMonInParty
jr UpdateEnemyMonInParty
UpdateBattleMonInParty::
; Update level, status, current HP
ld a, [wCurBattleMon]
UpdateBattleMon::
ld hl, wPartyMon1Level
call GetPartyLocation
ld d, h
ld e, l
ld hl, wBattleMonLevel
ld bc, wBattleMonMaxHP - wBattleMonLevel
jp CopyBytes
UpdateEnemyMonInParty::
; Update level, status, current HP
; No wildmons.
ld a, [wBattleMode]
dec a
ret z
ld a, [wCurOTMon]
ld hl, wOTPartyMon1Level
call GetPartyLocation
ld d, h
ld e, l
ld hl, wEnemyMonLevel
ld bc, wEnemyMonMaxHP - wEnemyMonLevel
jp CopyBytes
RefreshBattleHuds::
call UpdateBattleHuds
ld c, 3
call DelayFrames
jp WaitBGMap
UpdateBattleHuds::
farcall UpdatePlayerHUD
farcall UpdateEnemyHUD
ret
INCLUDE "home/battle_vars.asm"
FarCopyRadioText::
inc hl
ldh a, [hROMBank]
push af
ld a, [hli]
ld e, a
ld a, [hli]
ld d, a
ld a, [hli]
ldh [hROMBank], a
ld [MBC3RomBank], a
ld a, e
ld l, a
ld a, d
ld h, a
ld de, wRadioText
ld bc, 2 * SCREEN_WIDTH
call CopyBytes
pop af
ldh [hROMBank], a
ld [MBC3RomBank], a
ret
MobileTextBorder::
; For mobile link battles only.
ld a, [wLinkMode]
cp LINK_MOBILE
ret c
; Draw a cell phone icon at the
; top right corner of the border.
hlcoord 19, 12
ld [hl], $5e ; top
hlcoord 19, 13
ld [hl], $5f ; bottom
ret
BattleTextbox::
; Open a textbox and print text at hl.
push hl
call SpeechTextbox
call MobileTextBorder
call UpdateSprites
call ApplyTilemap
pop hl
call PrintTextboxText
ret
StdBattleTextbox::
; Open a textbox and print battle text at 20:hl.
ldh a, [hROMBank]
push af
ld a, BANK(BattleText)
rst Bankswitch
call BattleTextbox
pop af
rst Bankswitch
ret
GetBattleAnimPointer::
ld a, BANK(BattleAnimations)
rst Bankswitch
ld a, [hli]
ld [wBattleAnimAddress], a
ld a, [hl]
ld [wBattleAnimAddress + 1], a
; ClearBattleAnims is the only function that calls this...
ld a, BANK(ClearBattleAnims)
rst Bankswitch
ret
GetBattleAnimByte::
push hl
push de
ld hl, wBattleAnimAddress
ld e, [hl]
inc hl
ld d, [hl]
ld a, BANK(BattleAnimations)
rst Bankswitch
ld a, [de]
ld [wBattleAnimByte], a
inc de
ld a, BANK(BattleAnimCommands)
rst Bankswitch
ld [hl], d
dec hl
ld [hl], e
pop de
pop hl
ld a, [wBattleAnimByte]
ret
PushLYOverrides::
ldh a, [hLCDCPointer]
and a
ret z
ld a, LOW(wLYOverridesBackup)
ld [wRequested2bppSource], a
ld a, HIGH(wLYOverridesBackup)
ld [wRequested2bppSource + 1], a
ld a, LOW(wLYOverrides)
ld [wRequested2bppDest], a
ld a, HIGH(wLYOverrides)
ld [wRequested2bppDest + 1], a
ld a, (wLYOverridesEnd - wLYOverrides) / LEN_2BPP_TILE
ld [wRequested2bppSize], a
ret

113
home/battle_vars.asm Normal file
View file

@ -0,0 +1,113 @@
GetBattleVar::
push hl
call GetBattleVarAddr
pop hl
ret
GetBattleVarAddr::
; Get variable from pair a, depending on whose turn it is.
; There are 21 variable pairs.
push bc
ld hl, BattleVarPairs
ld c, a
ld b, 0
add hl, bc
add hl, bc
ld a, [hli]
ld h, [hl]
ld l, a
; Enemy turn uses the second byte instead.
; This lets battle variable calls be side-neutral.
ldh a, [hBattleTurn]
and a
jr z, .getvar
inc hl
.getvar
; var id
ld a, [hl]
ld c, a
ld b, 0
ld hl, BattleVarLocations
add hl, bc
add hl, bc
ld a, [hli]
ld h, [hl]
ld l, a
ld a, [hl]
pop bc
ret
BattleVarPairs:
; entries correspond to BATTLE_VARS_* constants
table_width 2, BattleVarPairs
dw .Substatus1
dw .Substatus2
dw .Substatus3
dw .Substatus4
dw .Substatus5
dw .Substatus1Opp
dw .Substatus2Opp
dw .Substatus3Opp
dw .Substatus4Opp
dw .Substatus5Opp
dw .Status
dw .StatusOpp
dw .MoveAnim
dw .MoveEffect
dw .MovePower
dw .MoveType
dw .CurMove
dw .LastCounter
dw .LastCounterOpp
dw .LastMove
dw .LastMoveOpp
assert_table_length NUM_BATTLE_VARS
; player enemy
.Substatus1: db PLAYER_SUBSTATUS_1, ENEMY_SUBSTATUS_1
.Substatus1Opp: db ENEMY_SUBSTATUS_1, PLAYER_SUBSTATUS_1
.Substatus2: db PLAYER_SUBSTATUS_2, ENEMY_SUBSTATUS_2
.Substatus2Opp: db ENEMY_SUBSTATUS_2, PLAYER_SUBSTATUS_2
.Substatus3: db PLAYER_SUBSTATUS_3, ENEMY_SUBSTATUS_3
.Substatus3Opp: db ENEMY_SUBSTATUS_3, PLAYER_SUBSTATUS_3
.Substatus4: db PLAYER_SUBSTATUS_4, ENEMY_SUBSTATUS_4
.Substatus4Opp: db ENEMY_SUBSTATUS_4, PLAYER_SUBSTATUS_4
.Substatus5: db PLAYER_SUBSTATUS_5, ENEMY_SUBSTATUS_5
.Substatus5Opp: db ENEMY_SUBSTATUS_5, PLAYER_SUBSTATUS_5
.Status: db PLAYER_STATUS, ENEMY_STATUS
.StatusOpp: db ENEMY_STATUS, PLAYER_STATUS
.MoveAnim: db PLAYER_MOVE_ANIMATION, ENEMY_MOVE_ANIMATION
.MoveEffect: db PLAYER_MOVE_EFFECT, ENEMY_MOVE_EFFECT
.MovePower: db PLAYER_MOVE_POWER, ENEMY_MOVE_POWER
.MoveType: db PLAYER_MOVE_TYPE, ENEMY_MOVE_TYPE
.CurMove: db PLAYER_CUR_MOVE, ENEMY_CUR_MOVE
.LastCounter: db PLAYER_COUNTER_MOVE, ENEMY_COUNTER_MOVE
.LastCounterOpp: db ENEMY_COUNTER_MOVE, PLAYER_COUNTER_MOVE
.LastMove: db PLAYER_LAST_MOVE, ENEMY_LAST_MOVE
.LastMoveOpp: db ENEMY_LAST_MOVE, PLAYER_LAST_MOVE
BattleVarLocations:
; entries correspond to PLAYER_* and ENEMY_* constants
table_width 2 + 2, BattleVarLocations
dw wPlayerSubStatus1, wEnemySubStatus1
dw wPlayerSubStatus2, wEnemySubStatus2
dw wPlayerSubStatus3, wEnemySubStatus3
dw wPlayerSubStatus4, wEnemySubStatus4
dw wPlayerSubStatus5, wEnemySubStatus5
dw wBattleMonStatus, wEnemyMonStatus
dw wPlayerMoveStructAnimation, wEnemyMoveStructAnimation
dw wPlayerMoveStructEffect, wEnemyMoveStructEffect
dw wPlayerMoveStructPower, wEnemyMoveStructPower
dw wPlayerMoveStructType, wEnemyMoveStructType
dw wCurPlayerMove, wCurEnemyMove
dw wLastPlayerCounterMove, wLastEnemyCounterMove
dw wLastPlayerMove, wLastEnemyMove
assert_table_length NUM_BATTLE_VAR_LOCATION_PAIRS

8
home/call_regs.asm Normal file
View file

@ -0,0 +1,8 @@
; Register aliases
_hl_::
jp hl
_de_::
push de
ret

23
home/clear_sprites.asm Normal file
View file

@ -0,0 +1,23 @@
ClearSprites::
; Erase OAM data
ld hl, wShadowOAM
ld b, wShadowOAMEnd - wShadowOAM
xor a
.loop
ld [hli], a
dec b
jr nz, .loop
ret
HideSprites::
; Set all OAM y-positions to 160 to hide them offscreen
ld hl, wShadowOAMSprite00YCoord
ld de, SPRITEOAMSTRUCT_LENGTH
ld b, NUM_SPRITE_OAM_STRUCTS
ld a, SCREEN_WIDTH_PX
.loop
ld [hl], a ; y
add hl, de
dec b
jr nz, .loop
ret

35
home/compare.asm Normal file
View file

@ -0,0 +1,35 @@
CompareBytes::
; Compare c bytes at de and hl.
; Return z if they all match.
.loop
ld a, [de]
cp [hl]
ret nz
inc de
inc hl
dec c
jr nz, .loop
ret
CompareBytesLong::
; Compare bc bytes at de and hl.
; Return carry if they all match.
.loop
ld a, [de]
cp [hl]
jr nz, .diff
inc de
inc hl
dec bc
ld a, b
or c
jr nz, .loop
scf
ret
.diff:
and a
ret

131
home/copy.asm Normal file
View file

@ -0,0 +1,131 @@
CopyBytes::
; copy bc bytes from hl to de
inc b ; we bail the moment b hits 0, so include the last run
inc c ; same thing; include last byte
jr .HandleLoop
.CopyByte:
ld a, [hli]
ld [de], a
inc de
.HandleLoop:
dec c
jr nz, .CopyByte
dec b
jr nz, .CopyByte
ret
SwapBytes::
; swap bc bytes between hl and de
.Loop:
; stash [hl] away on the stack
ld a, [hl]
push af
; copy a byte from [de] to [hl]
ld a, [de]
ld [hli], a
; retrieve the previous value of [hl]; put it in [de]
pop af
ld [de], a
inc de
; handle loop stuff
dec bc
ld a, b
or c
jr nz, .Loop
ret
ByteFill::
; fill bc bytes with the value of a, starting at hl
inc b ; we bail the moment b hits 0, so include the last run
inc c ; same thing; include last byte
jr .HandleLoop
.PutByte:
ld [hli], a
.HandleLoop:
dec c
jr nz, .PutByte
dec b
jr nz, .PutByte
ret
GetFarByte::
; retrieve a single byte from a:hl, and return it in a.
; bankswitch to new bank
ldh [hTempBank], a
ldh a, [hROMBank]
push af
ldh a, [hTempBank]
rst Bankswitch
; get byte from new bank
ld a, [hl]
ldh [hFarByte], a
; bankswitch to previous bank
pop af
rst Bankswitch
; return retrieved value in a
ldh a, [hFarByte]
ret
GetFarWord::
; retrieve a word from a:hl, and return it in hl.
; bankswitch to new bank
ldh [hTempBank], a
ldh a, [hROMBank]
push af
ldh a, [hTempBank]
rst Bankswitch
; get word from new bank, put it in hl
ld a, [hli]
ld h, [hl]
ld l, a
; bankswitch to previous bank and return
pop af
rst Bankswitch
ret
FarCopyWRAM::
ldh [hTempBank], a
ldh a, [rSVBK]
push af
ldh a, [hTempBank]
ldh [rSVBK], a
call CopyBytes
pop af
ldh [rSVBK], a
ret
GetFarWRAMByte::
ldh [hTempBank], a
ldh a, [rSVBK]
push af
ldh a, [hTempBank]
ldh [rSVBK], a
ld a, [hl]
ldh [hFarByte], a
pop af
ldh [rSVBK], a
ldh a, [hFarByte]
ret
GetFarWRAMWord:: ; unreferenced
ldh [hTempBank], a
ldh a, [rSVBK]
push af
ldh a, [hTempBank]
ldh [rSVBK], a
ld a, [hli]
ld h, [hl]
ld l, a
pop af
ldh [rSVBK], a
ret

36
home/copy_name.asm Normal file
View file

@ -0,0 +1,36 @@
CopyName1::
; Copies the name from de to wStringBuffer2
ld hl, wStringBuffer2
CopyName2::
; Copies the name from de to hl
.loop
ld a, [de]
inc de
ld [hli], a
cp "@"
jr nz, .loop
ret
CopyStringWithTerminator::
; in: hl = source, de = destination, c = length (non-zero)
; out: clobbers all but b
dec c
.copy_loop
ld a, [hli]
ld [de], a
inc de
cp "@"
jr z, .clear_loop
dec c
jr nz, .copy_loop
ld a, "@"
ld [de], a
ret
.clear_loop
ld [de], a
inc de
dec c
jr nz, .clear_loop
ret

35
home/copy_tilemap.asm Normal file
View file

@ -0,0 +1,35 @@
LoadTilemapToTempTilemap::
; Load wTilemap into wTempTilemap
ldh a, [rSVBK]
push af
ld a, BANK(wTempTilemap)
ldh [rSVBK], a
hlcoord 0, 0
decoord 0, 0, wTempTilemap
ld bc, wTilemapEnd - wTilemap
call CopyBytes
pop af
ldh [rSVBK], a
ret
SafeLoadTempTilemapToTilemap::
xor a
ldh [hBGMapMode], a
call LoadTempTilemapToTilemap
ld a, 1
ldh [hBGMapMode], a
ret
LoadTempTilemapToTilemap::
; Load wTempTilemap into wTilemap
ldh a, [rSVBK]
push af
ld a, BANK(wTempTilemap)
ldh [rSVBK], a
hlcoord 0, 0, wTempTilemap
decoord 0, 0
ld bc, wTilemapEnd - wTilemap
call CopyBytes
pop af
ldh [rSVBK], a
ret

317
home/decompress.asm Normal file
View file

@ -0,0 +1,317 @@
FarDecompress::
; Decompress graphics data from a:hl to de.
ld [wLZBank], a
ldh a, [hROMBank]
push af
ld a, [wLZBank]
rst Bankswitch
call Decompress
pop af
rst Bankswitch
ret
Decompress::
; Pokemon GSC uses an lz variant (lz3) for compression.
; This is mainly (but not necessarily) used for graphics.
; This function decompresses lz-compressed data from hl to de.
DEF LZ_END EQU $ff ; Compressed data is terminated with $ff.
; A typical control command consists of:
DEF LZ_CMD EQU %11100000 ; command id (bits 5-7)
DEF LZ_LEN EQU %00011111 ; length n (bits 0-4)
; Additional parameters are read during command execution.
; Commands:
DEF LZ_LITERAL EQU 0 << 5 ; Read literal data for n bytes.
DEF LZ_ITERATE EQU 1 << 5 ; Write the same byte for n bytes.
DEF LZ_ALTERNATE EQU 2 << 5 ; Alternate two bytes for n bytes.
DEF LZ_ZERO EQU 3 << 5 ; Write 0 for n bytes.
; Another class of commands reuses data from the decompressed output.
DEF LZ_RW EQU 2 + 5 ; bit
; These commands take a signed offset to start copying from.
; Wraparound is simulated.
; Positive offsets (15-bit) are added to the start address.
; Negative offsets (7-bit) are subtracted from the current position.
DEF LZ_REPEAT EQU 4 << 5 ; Repeat n bytes from the offset.
DEF LZ_FLIP EQU 5 << 5 ; Repeat n bitflipped bytes.
DEF LZ_REVERSE EQU 6 << 5 ; Repeat n bytes in reverse.
; If the value in the count needs to be larger than 5 bits,
; LZ_LONG can be used to expand the count to 10 bits.
DEF LZ_LONG EQU 7 << 5
; A new control command is read in bits 2-4.
; The top two bits of the length are bits 0-1.
; Another byte is read containing the bottom 8 bits.
DEF LZ_LONG_HI EQU %00000011
; In other words, the structure of the command becomes
; 111xxxyy yyyyyyyy
; x: the new control command
; y: the length
; Save the output address
; for rewrite commands.
ld a, e
ld [wLZAddress], a
ld a, d
ld [wLZAddress + 1], a
.Main:
ld a, [hl]
cp LZ_END
ret z
and LZ_CMD
cp LZ_LONG
jr nz, .short
; The count is now 10 bits.
; Read the next 3 bits.
; %00011100 -> %11100000
ld a, [hl]
add a
add a ; << 3
add a
; This is our new control code.
and LZ_CMD
push af
ld a, [hli]
and LZ_LONG_HI
ld b, a
ld a, [hli]
ld c, a
; read at least 1 byte
inc bc
jr .command
.short
push af
ld a, [hli]
and LZ_LEN
ld c, a
ld b, 0
; read at least 1 byte
inc c
.command
; Increment loop counts.
; We bail the moment they hit 0.
inc b
inc c
pop af
bit LZ_RW, a
jr nz, .rewrite
cp LZ_ITERATE
jr z, .Iter
cp LZ_ALTERNATE
jr z, .Alt
cp LZ_ZERO
jr z, .Zero
; Literal
; Read literal data for bc bytes.
.lloop
dec c
jr nz, .lnext
dec b
jp z, .Main
.lnext
ld a, [hli]
ld [de], a
inc de
jr .lloop
.Iter:
; Write the same byte for bc bytes.
ld a, [hli]
.iloop
dec c
jr nz, .inext
dec b
jp z, .Main
.inext
ld [de], a
inc de
jr .iloop
.Alt:
; Alternate two bytes for bc bytes.
dec c
jr nz, .anext1
dec b
jp z, .adone1
.anext1
ld a, [hli]
ld [de], a
inc de
dec c
jr nz, .anext2
dec b
jp z, .adone2
.anext2
ld a, [hld]
ld [de], a
inc de
jr .Alt
; Skip past the bytes we were alternating.
.adone1
inc hl
.adone2
inc hl
jr .Main
.Zero:
; Write 0 for bc bytes.
xor a
.zloop
dec c
jr nz, .znext
dec b
jp z, .Main
.znext
ld [de], a
inc de
jr .zloop
.rewrite
; Repeat decompressed data from output.
push hl
push af
ld a, [hli]
bit 7, a ; sign
jr z, .positive
; negative
; hl = de + -a
and %01111111
cpl
add e
ld l, a
ld a, -1
adc d
ld h, a
jr .ok
.positive
; Positive offsets are two bytes.
ld l, [hl]
ld h, a
; add to starting output address
ld a, [wLZAddress]
add l
ld l, a
ld a, [wLZAddress + 1]
adc h
ld h, a
.ok
pop af
cp LZ_REPEAT
jr z, .Repeat
cp LZ_FLIP
jr z, .Flip
cp LZ_REVERSE
jr z, .Reverse
; Since LZ_LONG is command 7,
; only commands 0-6 are passed in.
; This leaves room for an extra command 7.
; However, lengths longer than 768
; would be interpreted as LZ_END.
; More practically, LZ_LONG is not recursive.
; For now, it defaults to LZ_REPEAT.
.Repeat:
; Copy decompressed data for bc bytes.
dec c
jr nz, .rnext
dec b
jr z, .donerw
.rnext
ld a, [hli]
ld [de], a
inc de
jr .Repeat
.Flip:
; Copy bitflipped decompressed data for bc bytes.
dec c
jr nz, .fnext
dec b
jp z, .donerw
.fnext
ld a, [hli]
push bc
lb bc, 0, 8
.floop
rra
rl b
dec c
jr nz, .floop
ld a, b
pop bc
ld [de], a
inc de
jr .Flip
.Reverse:
; Copy reversed decompressed data for bc bytes.
dec c
jr nz, .rvnext
dec b
jp z, .donerw
.rvnext
ld a, [hld]
ld [de], a
inc de
jr .Reverse
.donerw
pop hl
bit 7, [hl]
jr nz, .next
inc hl ; positive offset is two bytes
.next
inc hl
jp .Main

20
home/delay.asm Normal file
View file

@ -0,0 +1,20 @@
DelayFrame::
; Wait for one frame
ld a, 1
ld [wVBlankOccurred], a
; Wait for the next VBlank, halting to conserve battery
.halt
halt
nop
ld a, [wVBlankOccurred]
and a
jr nz, .halt
ret
DelayFrames::
; Wait c frames
call DelayFrame
dec c
jr nz, DelayFrames
ret

27
home/double_speed.asm Normal file
View file

@ -0,0 +1,27 @@
; The CGB hardware introduces Double Speed Mode.
; While active, the clock speed is doubled.
; The hardware can switch between normal speed
; and double speed at any time, but LCD output
; collapses during the switch.
DoubleSpeed::
ld hl, rKEY1
bit 7, [hl]
jr z, SwitchSpeed
ret
NormalSpeed::
ld hl, rKEY1
bit 7, [hl]
ret z
SwitchSpeed::
set 0, [hl]
xor a
ldh [rIF], a
ldh [rIE], a
ld a, $30
ldh [rJOYP], a
stop ; rgbasm adds a nop after this instruction by default
ret

107
home/fade.asm Normal file
View file

@ -0,0 +1,107 @@
; Functions to fade the screen in and out.
RotateFourPalettesRight::
ldh a, [hCGB]
and a
jr z, .dmg
ld hl, IncGradGBPalTable_00
ld b, 4
jr RotatePalettesRight
.dmg
ld hl, IncGradGBPalTable_08
ld b, 4
jr RotatePalettesRight
RotateThreePalettesRight::
ldh a, [hCGB]
and a
jr z, .dmg
ld hl, IncGradGBPalTable_05
ld b, 3
jr RotatePalettesRight
.dmg
ld hl, IncGradGBPalTable_13
ld b, 3
RotatePalettesRight::
; Rotate palettes to the right and fill with loaded colors from the left
; If we're already at the leftmost color, fill with the leftmost color
push de
ld a, [hli]
call DmgToCgbBGPals
ld a, [hli]
ld e, a
ld a, [hli]
ld d, a
call DmgToCgbObjPals
ld c, 8
call DelayFrames
pop de
dec b
jr nz, RotatePalettesRight
ret
RotateFourPalettesLeft::
ldh a, [hCGB]
and a
jr z, .dmg
ld hl, IncGradGBPalTable_04 - 1
ld b, 4
jr RotatePalettesLeft
.dmg
ld hl, IncGradGBPalTable_12 - 1
ld b, 4
jr RotatePalettesLeft
RotateThreePalettesLeft::
ldh a, [hCGB]
and a
jr z, .dmg
ld hl, IncGradGBPalTable_07 - 1
ld b, 3
jr RotatePalettesLeft
.dmg
ld hl, IncGradGBPalTable_15 - 1
ld b, 3
RotatePalettesLeft::
; Rotate palettes to the left and fill with loaded colors from the right
; If we're already at the rightmost color, fill with the rightmost color
push de
ld a, [hld]
ld d, a
ld a, [hld]
ld e, a
call DmgToCgbObjPals
ld a, [hld]
call DmgToCgbBGPals
ld c, 8
call DelayFrames
pop de
dec b
jr nz, RotatePalettesLeft
ret
IncGradGBPalTable_00:: dc 3,3,3,3, 3,3,3,3, 3,3,3,3
IncGradGBPalTable_01:: dc 3,3,3,2, 3,3,3,2, 3,3,3,2
IncGradGBPalTable_02:: dc 3,3,2,1, 3,3,2,1, 3,3,2,1
IncGradGBPalTable_03:: dc 3,2,1,0, 3,2,1,0, 3,2,1,0
IncGradGBPalTable_04:: dc 3,2,1,0, 3,2,1,0, 3,2,1,0
IncGradGBPalTable_05:: dc 2,1,0,0, 2,1,0,0, 2,1,0,0
IncGradGBPalTable_06:: dc 1,0,0,0, 1,0,0,0, 1,0,0,0
IncGradGBPalTable_07:: dc 0,0,0,0, 0,0,0,0, 0,0,0,0
; bgp obp1 obp2
IncGradGBPalTable_08:: dc 3,3,3,3, 3,3,3,3, 3,3,3,3
IncGradGBPalTable_09:: dc 3,3,3,2, 3,3,3,2, 3,3,2,0
IncGradGBPalTable_10:: dc 3,3,2,1, 3,2,1,0, 3,2,1,0
IncGradGBPalTable_11:: dc 3,2,1,0, 3,1,0,0, 3,2,0,0
IncGradGBPalTable_12:: dc 3,2,1,0, 3,1,0,0, 3,2,0,0
IncGradGBPalTable_13:: dc 2,1,0,0, 2,0,0,0, 2,1,0,0
IncGradGBPalTable_14:: dc 1,0,0,0, 1,0,0,0, 1,0,0,0
IncGradGBPalTable_15:: dc 0,0,0,0, 0,0,0,0, 0,0,0,0

48
home/farcall.asm Normal file
View file

@ -0,0 +1,48 @@
FarCall_de::
; Call a:de.
; Preserves other registers.
ldh [hTempBank], a
ldh a, [hROMBank]
push af
ldh a, [hTempBank]
rst Bankswitch
call FarCall_JumpToDE
jr ReturnFarCall
FarCall_JumpToDE:
push de
ret
FarCall_hl::
; Call a:hl.
; Preserves other registers.
ldh [hTempBank], a
ldh a, [hROMBank]
push af
ldh a, [hTempBank]
rst Bankswitch
call FarCall_JumpToHL
; fallthrough
ReturnFarCall::
; We want to retain the contents of f.
; To do this, we can pop to bc instead of af.
ld a, b
ld [wFarCallBC], a
ld a, c
ld [wFarCallBC + 1], a
; Restore the working bank.
pop bc
ld a, b
rst Bankswitch
; Restore the contents of bc.
ld a, [wFarCallBC]
ld b, a
ld a, [wFarCallBC + 1]
ld c, a
ret
FarCall_JumpToHL::
jp hl

113
home/flag.asm Normal file
View file

@ -0,0 +1,113 @@
ResetMapBufferEventFlags::
xor a
ld hl, wEventFlags
ld [hli], a
ret
ResetBikeFlags::
xor a
ld hl, wBikeFlags
ld [hli], a
ld [hl], a
ret
ResetFlashIfOutOfCave::
ld a, [wEnvironment]
cp ROUTE
jr z, .outdoors
cp TOWN
jr z, .outdoors
ret
.outdoors
ld hl, wStatusFlags
res STATUSFLAGS_FLASH_F, [hl]
ret
EventFlagAction::
ld hl, wEventFlags
call FlagAction
ret
FlagAction::
; Perform action b on bit de in flag array hl.
; inputs:
; b: function
; 0 RESET_FLAG clear bit
; 1 SET_FLAG set bit
; 2 CHECK_FLAG check bit
; de: bit number
; hl: pointer to the flag array
; get index within the byte
ld a, e
and 7
; shift de right by three bits (get the index within memory)
rept 3
srl d
rr e
endr
add hl, de
; implement a decoder
ld c, 1
rrca
jr nc, .one
rlc c
.one
rrca
jr nc, .two
rlc c
rlc c
.two
rrca
jr nc, .three
swap c
.three
; check b's value: 0, 1, 2
ld a, b
cp SET_FLAG
jr c, .clearbit ; RESET_FLAG
jr z, .setbit ; SET_FLAG
; check bit
ld a, [hl]
and c
ld c, a
ret
.setbit
; set bit
ld a, [hl]
or c
ld [hl], a
ret
.clearbit
; clear bit
ld a, c
cpl
and [hl]
ld [hl], a
ret
CheckReceivedDex::
ld de, ENGINE_POKEDEX
ld b, CHECK_FLAG
farcall EngineFlagAction
ld a, c
and a
ret
xor_a::
xor a
ret
xor_a_dec_a::
xor a
dec a
ret

118
home/game_time.asm Normal file
View file

@ -0,0 +1,118 @@
ResetGameTime::
xor a
ld [wGameTimeCap], a
ld [wGameTimeHours], a
ld [wGameTimeHours + 1], a
ld [wGameTimeMinutes], a
ld [wGameTimeSeconds], a
ld [wGameTimeFrames], a
ret
GameTimer::
nop
ldh a, [rSVBK]
push af
ld a, BANK(wGameTime)
ldh [rSVBK], a
call .Function
pop af
ldh [rSVBK], a
ret
.Function:
; Increment the game timer by one frame.
; The game timer is capped at 999:59:59.00.
; Don't update if game logic is paused.
ld a, [wGameLogicPaused]
and a
ret nz
; Is the timer paused?
ld hl, wGameTimerPaused
bit GAME_TIMER_PAUSED_F, [hl]
ret z
; Is the timer already capped?
ld hl, wGameTimeCap
bit 0, [hl]
ret nz
; +1 frame
ld hl, wGameTimeFrames
ld a, [hl]
inc a
cp 60 ; frames/second
jr nc, .second
ld [hl], a
ret
.second
xor a
ld [hl], a
; +1 second
ld hl, wGameTimeSeconds
ld a, [hl]
inc a
cp 60 ; seconds/minute
jr nc, .minute
ld [hl], a
ret
.minute
xor a
ld [hl], a
; +1 minute
ld hl, wGameTimeMinutes
ld a, [hl]
inc a
cp 60 ; minutes/hour
jr nc, .hour
ld [hl], a
ret
.hour
xor a
ld [hl], a
; +1 hour
ld a, [wGameTimeHours]
ld h, a
ld a, [wGameTimeHours + 1]
ld l, a
inc hl
; Cap the timer after 1000 hours.
ld a, h
cp HIGH(1000)
jr c, .ok
ld a, l
cp LOW(1000)
jr c, .ok
ld hl, wGameTimeCap
set 0, [hl]
ld a, 59 ; 999:59:59.00
ld [wGameTimeMinutes], a
ld [wGameTimeSeconds], a
ret
.ok
ld a, h
ld [wGameTimeHours], a
ld a, l
ld [wGameTimeHours + 1], a
ret

392
home/gfx.asm Normal file
View file

@ -0,0 +1,392 @@
DEF TILES_PER_CYCLE EQU 8
DEF MOBILE_TILES_PER_CYCLE EQU 6
Get2bppViaHDMA::
ldh a, [rLCDC]
bit rLCDC_ENABLE, a
jp z, Copy2bpp
homecall HDMATransfer2bpp
ret
Get1bppViaHDMA::
ldh a, [rLCDC]
bit rLCDC_ENABLE, a
jp z, Copy1bpp
homecall HDMATransfer1bpp
ret
FarCopyBytesDouble_DoubleBankSwitch::
ldh [hTempBank], a
ldh a, [hROMBank]
push af
ldh a, [hTempBank]
rst Bankswitch
call FarCopyBytesDouble
pop af
rst Bankswitch
ret
SafeHDMATransfer: ; unreferenced
dec c
ldh a, [hBGMapMode]
push af
xor a
ldh [hBGMapMode], a
ldh a, [hROMBank]
push af
ld a, b
rst Bankswitch
.loop
; load the source and target MSB and LSB
ld a, d
ldh [rHDMA1], a ; source MSB
ld a, e
and $f0
ldh [rHDMA2], a ; source LSB
ld a, h
and $1f
ldh [rHDMA3], a ; target MSB
ld a, l
and $f0
ldh [rHDMA4], a ; target LSB
; stop when c < TILES_PER_CYCLE
ld a, c
cp TILES_PER_CYCLE
jr c, .done
; decrease c by TILES_PER_CYCLE
sub TILES_PER_CYCLE
ld c, a
; DMA transfer state
ld a, $f
ldh [hDMATransfer], a
call DelayFrame
; add $100 to hl and de
ld a, l
add LOW($100)
ld l, a
ld a, h
adc HIGH($100)
ld h, a
ld a, e
add LOW($100)
ld e, a
ld a, d
adc HIGH($100)
ld d, a
jr .loop
.done
ld a, c
and $7f ; pretty silly, considering at most bits 0-2 would be set
ldh [hDMATransfer], a
call DelayFrame
pop af
rst Bankswitch
pop af
ldh [hBGMapMode], a
ret
UpdatePlayerSprite::
farcall _UpdatePlayerSprite
ret
LoadStandardFont::
farcall _LoadStandardFont
ret
LoadFontsBattleExtra::
farcall _LoadFontsBattleExtra
ret
LoadFontsExtra::
farcall _LoadFontsExtra1
farcall _LoadFontsExtra2
ret
LoadFontsExtra2: ; unreferenced
farcall _LoadFontsExtra2
ret
DecompressRequest2bpp::
push de
ld a, BANK(sScratch)
call OpenSRAM
push bc
ld de, sScratch
ld a, b
call FarDecompress
pop bc
pop hl
ld de, sScratch
call Request2bpp
call CloseSRAM
ret
FarCopyBytes::
; copy bc bytes from a:hl to de
ldh [hTempBank], a
ldh a, [hROMBank]
push af
ldh a, [hTempBank]
rst Bankswitch
call CopyBytes
pop af
rst Bankswitch
ret
FarCopyBytesDouble:
; Copy bc bytes from a:hl to bc*2 bytes at de,
; doubling each byte in the process.
ldh [hTempBank], a
ldh a, [hROMBank]
push af
ldh a, [hTempBank]
rst Bankswitch
; switcheroo, de <> hl
ld a, h
ld h, d
ld d, a
ld a, l
ld l, e
ld e, a
inc b
inc c
jr .dec
.loop
ld a, [de]
inc de
ld [hli], a
ld [hli], a
.dec
dec c
jr nz, .loop
dec b
jr nz, .loop
pop af
rst Bankswitch
ret
Request2bpp::
; Load 2bpp at b:de to occupy c tiles of hl.
ldh a, [hBGMapMode]
push af
xor a
ldh [hBGMapMode], a
ldh a, [hROMBank]
push af
ld a, b
rst Bankswitch
ldh a, [hTilesPerCycle]
push af
ld a, TILES_PER_CYCLE
ldh [hTilesPerCycle], a
ld a, [wLinkMode]
cp LINK_MOBILE
jr nz, .NotMobile
ldh a, [hMobile]
and a
jr nz, .NotMobile
ld a, MOBILE_TILES_PER_CYCLE
ldh [hTilesPerCycle], a
.NotMobile:
ld a, e
ld [wRequested2bppSource], a
ld a, d
ld [wRequested2bppSource + 1], a
ld a, l
ld [wRequested2bppDest], a
ld a, h
ld [wRequested2bppDest + 1], a
.loop
ld a, c
ld hl, hTilesPerCycle
cp [hl]
jr nc, .cycle
ld [wRequested2bppSize], a
.wait
call DelayFrame
ld a, [wRequested2bppSize]
and a
jr nz, .wait
pop af
ldh [hTilesPerCycle], a
pop af
rst Bankswitch
pop af
ldh [hBGMapMode], a
ret
.cycle
ldh a, [hTilesPerCycle]
ld [wRequested2bppSize], a
.wait2
call DelayFrame
ld a, [wRequested2bppSize]
and a
jr nz, .wait2
ld a, c
ld hl, hTilesPerCycle
sub [hl]
ld c, a
jr .loop
Request1bpp::
; Load 1bpp at b:de to occupy c tiles of hl.
ldh a, [hBGMapMode]
push af
xor a
ldh [hBGMapMode], a
ldh a, [hROMBank]
push af
ld a, b
rst Bankswitch
ldh a, [hTilesPerCycle]
push af
ld a, TILES_PER_CYCLE
ldh [hTilesPerCycle], a
ld a, [wLinkMode]
cp LINK_MOBILE
jr nz, .NotMobile
ldh a, [hMobile]
and a
jr nz, .NotMobile
ld a, MOBILE_TILES_PER_CYCLE
ldh [hTilesPerCycle], a
.NotMobile:
ld a, e
ld [wRequested1bppSource], a
ld a, d
ld [wRequested1bppSource + 1], a
ld a, l
ld [wRequested1bppDest], a
ld a, h
ld [wRequested1bppDest + 1], a
.loop
ld a, c
ld hl, hTilesPerCycle
cp [hl]
jr nc, .cycle
ld [wRequested1bppSize], a
.wait
call DelayFrame
ld a, [wRequested1bppSize]
and a
jr nz, .wait
pop af
ldh [hTilesPerCycle], a
pop af
rst Bankswitch
pop af
ldh [hBGMapMode], a
ret
.cycle
ldh a, [hTilesPerCycle]
ld [wRequested1bppSize], a
.wait2
call DelayFrame
ld a, [wRequested1bppSize]
and a
jr nz, .wait2
ld a, c
ld hl, hTilesPerCycle
sub [hl]
ld c, a
jr .loop
Get2bpp::
; copy c 2bpp tiles from b:de to hl
ldh a, [rLCDC]
bit rLCDC_ENABLE, a
jp nz, Request2bpp
; fallthrough
Copy2bpp:
push hl
ld h, d
ld l, e
pop de
; bank
ld a, b
; bc = c * LEN_2BPP_TILE
push af
swap c
ld a, $f
and c
ld b, a
ld a, $f0
and c
ld c, a
pop af
jp FarCopyBytes
Get1bpp::
; copy c 1bpp tiles from b:de to hl
ldh a, [rLCDC]
bit rLCDC_ENABLE, a
jp nz, Request1bpp
; fallthrough
Copy1bpp::
push de
ld d, h
ld e, l
; bank
ld a, b
; bc = c * LEN_1BPP_TILE
push af
ld h, 0
ld l, c
add hl, hl
add hl, hl
add hl, hl
ld b, h
ld c, l
pop af
pop hl
jp FarCopyBytesDouble

70
home/header.asm Normal file
View file

@ -0,0 +1,70 @@
; rst vectors (called through the rst instruction)
SECTION "rst0", ROM0[$0000]
di
jp Start
SECTION "rst8", ROM0[$0008]
FarCall::
jp FarCall_hl
SECTION "rst10", ROM0[$0010]
Bankswitch::
ldh [hROMBank], a
ld [MBC3RomBank], a
ret
SECTION "rst18", ROM0[$0018]
rst $38
SECTION "rst20", ROM0[$0020]
rst $38
SECTION "rst28", ROM0[$0028]
JumpTable::
push de
ld e, a
ld d, 0
add hl, de
add hl, de
ld a, [hli]
ld h, [hl]
; SECTION "rst30", ROM0[$0030]
ld l, a
pop de
jp hl
SECTION "rst38", ROM0[$0038]
rst $38
; Game Boy hardware interrupts
SECTION "vblank", ROM0[$0040]
jp VBlank
SECTION "lcd", ROM0[$0048]
jp LCD
SECTION "timer", ROM0[$0050]
jp MobileTimer
SECTION "serial", ROM0[$0058]
jp Serial
SECTION "joypad", ROM0[$0060]
jp Joypad
SECTION "Header", ROM0[$0100]
Start::
; Nintendo requires all Game Boy ROMs to begin with a nop ($00) and a jp ($C3)
; to the starting address.
nop
jp _Start
; The Game Boy cartridge header data is patched over by rgbfix.
; This makes sure it doesn't get used for anything else.
ds $0150 - @, $00

28
home/hm_moves.asm Normal file
View file

@ -0,0 +1,28 @@
; HM moves can't be forgotten
IsHM::
cp HM01
jr c, .NotHM
scf
ret
.NotHM:
and a
ret
IsHMMove::
call GetMoveIndexFromID
ld b, h
ld c, l
ld hl, .HMMoves
ld de, 2
jp IsInWordArray
.HMMoves:
dw CUT
dw FLY
dw SURF
dw STRENGTH
dw FLASH
dw WATERFALL
dw WHIRLPOOL
dw -1 ; end

76
home/indirection.asm Normal file
View file

@ -0,0 +1,76 @@
LoadIndirectPointer::
; in: a:hl: indirect table, bc: index
; out: a:hl: pointer to element, b: copy of a, c: clobbered, de: preserved, zero flag: set if pointer is null
push de
ld d, a
ldh a, [hROMBank]
push af
ld a, d
rst Bankswitch
ld a, [hli]
ld e, a
ld a, [hli]
ld d, a
add a, a
jr nc, .loop
res 7, d
dec bc
ld a, b
and c
inc a
jr nz, .loop
.null
ld hl, 0
ld b, h
.done
pop af
rst Bankswitch
pop de
ld a, h
or l
ld a, b
ret
.next
inc hl
inc hl
inc hl
cpl
inc a
add a, c
ld c, a
jr c, .loop ;subtracting by addition has the carry flipped
dec b
.loop
ld a, [hli]
and a
jr z, .null
inc b
dec b
jr nz, .next
cp c
jr c, .next
jr z, .next
ld a, [hli]
ld b, a
ld a, [hli]
ld h, [hl]
ld l, a
or h
jr z, .done ;no point in setting them to zero if they already are
ld a, c
ld c, e
ld e, b
ld b, d
call AddNTimes
ld b, e
jr .done
LoadDoubleIndirectPointer::
; for indirect tables of near pointers to data, used for structures of varying size
; same calling convention as LoadIndirectPointer
call LoadIndirectPointer
call nz, GetFarWord
ld a, b
ret

216
home/init.asm Normal file
View file

@ -0,0 +1,216 @@
Reset::
di
call InitSound
xor a
ldh [hMapAnims], a
call ClearPalettes
xor a
ldh [rIF], a
ld a, 1 << VBLANK
ldh [rIE], a
ei
ld hl, wJoypadDisable
set JOYPAD_DISABLE_SGB_TRANSFER_F, [hl]
ld c, 32
call DelayFrames
jr Init
_Start::
cp $11
jr z, .cgb
xor a ; FALSE
jr .load
.cgb
ld a, TRUE
.load
ldh [hCGB], a
ld a, TRUE
ldh [hSystemBooted], a
Init::
di
xor a
ldh [rIF], a
ldh [rIE], a
ldh [rRP], a
ldh [rSCX], a
ldh [rSCY], a
ldh [rSB], a
ldh [rSC], a
ldh [rWX], a
ldh [rWY], a
ldh [rBGP], a
ldh [rOBP0], a
ldh [rOBP1], a
ldh [rTMA], a
ldh [rTAC], a
ld [wBetaTitleSequenceOpeningType], a
ld a, %100 ; Start timer at 4096Hz
ldh [rTAC], a
.wait
ldh a, [rLY]
cp LY_VBLANK + 1
jr nz, .wait
xor a
ldh [rLCDC], a
; Clear WRAM bank 0
ld hl, WRAM0_Begin
ld bc, WRAM0_End - WRAM0_Begin
.ByteFill:
ld [hl], 0
inc hl
dec bc
ld a, b
or c
jr nz, .ByteFill
ld sp, wStackTop
; Clear HRAM
ldh a, [hCGB]
push af
ldh a, [hSystemBooted]
push af
xor a
ld hl, HRAM_Begin
ld bc, HRAM_End - HRAM_Begin
call ByteFill
pop af
ldh [hSystemBooted], a
pop af
ldh [hCGB], a
ld a, -1
ldh [hSRAMBank], a
call ClearWRAM
ld a, 1
ldh [rSVBK], a
call ClearVRAM
call ClearSprites
call ClearsScratch
ld a, BANK(WriteOAMDMACodeToHRAM) ; aka BANK(GameInit)
rst Bankswitch
call WriteOAMDMACodeToHRAM
xor a
ldh [hMapAnims], a
ldh [hSCX], a
ldh [hSCY], a
ldh [rJOYP], a
ld a, $8 ; HBlank int enable
ldh [rSTAT], a
ld a, $90
ldh [hWY], a
ldh [rWY], a
ld a, 7
ldh [hWX], a
ldh [rWX], a
ld a, LCDC_DEFAULT ; %11100011
; LCD on
; Win tilemap 1
; Win on
; BG/Win tiledata 0
; BG Tilemap 0
; OBJ 8x8
; OBJ on
; BG on
ldh [rLCDC], a
ld a, CONNECTION_NOT_ESTABLISHED
ldh [hSerialConnectionStatus], a
farcall InitCGBPals
ld a, HIGH(vBGMap1)
ldh [hBGMapAddress + 1], a
xor a ; LOW(vBGMap1)
ldh [hBGMapAddress], a
farcall StartClock
xor a ; SRAM_DISABLE
ld [MBC3LatchClock], a
ld [MBC3SRamEnable], a
ldh a, [hCGB]
and a
jr z, .no_double_speed
call NormalSpeed
.no_double_speed
xor a
ldh [rIF], a
ld a, IE_DEFAULT
ldh [rIE], a
ei
call DelayFrame
predef InitSGBBorder
call InitSound
xor a
ld [wMapMusic], a
jp GameInit
ClearVRAM::
; Wipe VRAM banks 0 and 1
ld a, 1
ldh [rVBK], a
call .clear
xor a ; 0
ldh [rVBK], a
.clear
ld hl, VRAM_Begin
ld bc, VRAM_End - VRAM_Begin
xor a
call ByteFill
ret
ClearWRAM::
; Wipe swappable WRAM banks (1-7)
; Assumes CGB or AGB
ld a, 1
.bank_loop
push af
ldh [rSVBK], a
xor a
ld hl, WRAM1_Begin
ld bc, WRAM1_End - WRAM1_Begin
call ByteFill
pop af
inc a
cp 8
jr c, .bank_loop
ret
ClearsScratch::
; Wipe the first 32 bytes of sScratch
ld a, BANK(sScratch)
call OpenSRAM
ld hl, sScratch
ld bc, $20
xor a
call ByteFill
call CloseSRAM
ret

70
home/item.asm Normal file
View file

@ -0,0 +1,70 @@
DoItemEffect::
farcall _DoItemEffect
ret
CheckTossableItem::
push hl
push de
push bc
farcall _CheckTossableItem
pop bc
pop de
pop hl
ret
TossItem::
push hl
push de
push bc
ldh a, [hROMBank]
push af
ld a, BANK(_TossItem)
rst Bankswitch
call _TossItem
pop bc
ld a, b
rst Bankswitch
pop bc
pop de
pop hl
ret
ReceiveItem::
push bc
ldh a, [hROMBank]
push af
ld a, BANK(_ReceiveItem)
rst Bankswitch
push hl
push de
call _ReceiveItem
pop de
pop hl
pop bc
ld a, b
rst Bankswitch
pop bc
ret
CheckItem::
push hl
push de
push bc
ldh a, [hROMBank]
push af
ld a, BANK(_CheckItem)
rst Bankswitch
call _CheckItem
pop bc
ld a, b
rst Bankswitch
pop bc
pop de
pop hl
ret

483
home/joypad.asm Normal file
View file

@ -0,0 +1,483 @@
Joypad::
; Replaced by UpdateJoypad, called from VBlank instead of the useless
; joypad interrupt.
; This is a placeholder in case the interrupt is somehow enabled.
reti
ClearJoypad::
xor a
; Pressed this frame (delta)
ldh [hJoyPressed], a
; Currently pressed
ldh [hJoyDown], a
ret
UpdateJoypad::
; This is called automatically every frame in VBlank.
; Read the joypad register and translate it to something more
; workable for use in-game. There are 8 buttons, so we can use
; one byte to contain all player input.
; Updates:
; hJoypadReleased: released this frame (delta)
; hJoypadPressed: pressed this frame (delta)
; hJoypadDown: currently pressed
; hJoypadSum: pressed so far
; Any of these three bits can be used to disable input.
ld a, [wJoypadDisable]
and (1 << JOYPAD_DISABLE_MON_FAINT_F) | (1 << JOYPAD_DISABLE_SGB_TRANSFER_F) | (1 << 4)
ret nz
; If we're saving, input is disabled.
ld a, [wGameLogicPaused]
and a
ret nz
; We can only get four inputs at a time.
; We take d-pad first for no particular reason.
ld a, R_DPAD
ldh [rJOYP], a
; Read twice to give the request time to take.
ldh a, [rJOYP]
ldh a, [rJOYP]
; The Joypad register output is in the lo nybble (inversed).
; We make the hi nybble of our new container d-pad input.
cpl
and $f
swap a
; We'll keep this in b for now.
ld b, a
; Buttons make 8 total inputs (A, B, Select, Start).
; We can fit this into one byte.
ld a, R_BUTTONS
ldh [rJOYP], a
; Wait for input to stabilize.
rept 6
ldh a, [rJOYP]
endr
; Buttons take the lo nybble.
cpl
and $f
or b
ld b, a
; Reset the joypad register since we're done with it.
ld a, $30
ldh [rJOYP], a
; To get the delta we xor the last frame's input with the new one.
ldh a, [hJoypadDown] ; last frame
ld e, a
xor b
ld d, a
; Released this frame:
and e
ldh [hJoypadReleased], a
; Pressed this frame:
ld a, d
and b
ldh [hJoypadPressed], a
; Add any new presses to the list of collective presses:
ld c, a
ldh a, [hJoypadSum]
or c
ldh [hJoypadSum], a
; Currently pressed:
ld a, b
ldh [hJoypadDown], a
; Now that we have the input, we can do stuff with it.
; For example, soft reset:
and A_BUTTON | B_BUTTON | SELECT | START
cp A_BUTTON | B_BUTTON | SELECT | START
jp z, Reset
ret
GetJoypad::
; Update mirror joypad input from hJoypadDown (real input)
; hJoyReleased: released this frame (delta)
; hJoyPressed: pressed this frame (delta)
; hJoyDown: currently pressed
; bit 0 A
; 1 B
; 2 SELECT
; 3 START
; 4 RIGHT
; 5 LEFT
; 6 UP
; 7 DOWN
push af
push hl
push de
push bc
; The player input can be automated using an input stream.
; See more below.
ld a, [wInputType]
cp AUTO_INPUT
jr z, .auto
; To get deltas, take this and last frame's input.
ldh a, [hJoypadDown] ; real input
ld b, a
ldh a, [hJoyDown] ; last frame mirror
ld e, a
; Released this frame:
xor b
ld d, a
and e
ldh [hJoyReleased], a
; Pressed this frame:
ld a, d
and b
ldh [hJoyPressed], a
; It looks like the collective presses got commented out here.
ld c, a
; Currently pressed:
ld a, b
ldh [hJoyDown], a ; frame input
.quit
pop bc
pop de
pop hl
pop af
ret
.auto
; Use a predetermined input stream (used in the catching tutorial).
; Stream format: [input][duration]
; A value of $ff will immediately end the stream.
; Read from the input stream.
ldh a, [hROMBank]
push af
ld a, [wAutoInputBank]
rst Bankswitch
ld hl, wAutoInputAddress
ld a, [hli]
ld h, [hl]
ld l, a
; We only update when the input duration has expired.
ld a, [wAutoInputLength]
and a
jr z, .updateauto
; Until then, don't change anything.
dec a
ld [wAutoInputLength], a
pop af
rst Bankswitch
jr .quit
.updateauto
; An input of $ff will end the stream.
ld a, [hli]
cp -1
jr z, .stopauto
ld b, a
; A duration of $ff will end the stream indefinitely.
ld a, [hli]
ld [wAutoInputLength], a
cp -1
jr nz, .next
; The current input is overwritten.
dec hl
dec hl
ld b, NO_INPUT
jr .finishauto
.next
; On to the next input...
ld a, l
ld [wAutoInputAddress], a
ld a, h
ld [wAutoInputAddress + 1], a
jr .finishauto
.stopauto
call StopAutoInput
ld b, NO_INPUT
.finishauto
pop af
rst Bankswitch
ld a, b
ldh [hJoyPressed], a ; pressed
ldh [hJoyDown], a ; input
jr .quit
StartAutoInput::
; Start reading automated input stream at a:hl.
ld [wAutoInputBank], a
ld a, l
ld [wAutoInputAddress], a
ld a, h
ld [wAutoInputAddress + 1], a
; Start reading the stream immediately.
xor a
ld [wAutoInputLength], a
; Reset input mirrors.
xor a
ldh [hJoyPressed], a ; pressed this frame
ldh [hJoyReleased], a ; released this frame
ldh [hJoyDown], a ; currently pressed
ld a, AUTO_INPUT
ld [wInputType], a
ret
StopAutoInput::
; Clear variables related to automated input.
xor a
ld [wAutoInputBank], a
ld [wAutoInputAddress], a
ld [wAutoInputAddress + 1], a
ld [wAutoInputLength], a
; Back to normal input.
ld [wInputType], a
ret
JoyTitleScreenInput:: ; unreferenced
.loop
call DelayFrame
push bc
call JoyTextDelay
pop bc
; Save data can be deleted by pressing Up + B + Select.
ldh a, [hJoyDown]
cp D_UP | SELECT | B_BUTTON
jr z, .keycombo
; Press Start or A to start the game.
ldh a, [hJoyLast]
and START | A_BUTTON
jr nz, .keycombo
dec c
jr nz, .loop
and a
ret
.keycombo
scf
ret
JoyWaitAorB::
.loop
call DelayFrame
call GetJoypad
ldh a, [hJoyPressed]
and A_BUTTON | B_BUTTON
ret nz
call UpdateTimeAndPals
jr .loop
WaitButton::
ldh a, [hOAMUpdate]
push af
ld a, 1
ldh [hOAMUpdate], a
call WaitBGMap
call JoyWaitAorB
pop af
ldh [hOAMUpdate], a
ret
JoyTextDelay::
call GetJoypad
ldh a, [hInMenu]
and a
ldh a, [hJoyPressed]
jr z, .ok
ldh a, [hJoyDown]
.ok
ldh [hJoyLast], a
ldh a, [hJoyPressed]
and a
jr z, .checkframedelay
ld a, 15
ld [wTextDelayFrames], a
ret
.checkframedelay
ld a, [wTextDelayFrames]
and a
jr z, .restartframedelay
xor a
ldh [hJoyLast], a
ret
.restartframedelay
ld a, 5
ld [wTextDelayFrames], a
ret
WaitPressAorB_BlinkCursor::
; Show a blinking cursor in the lower right-hand
; corner of a textbox and wait until A or B is
; pressed.
;
; NOTE: The cursor has to be shown before calling
; this function or no cursor will be shown at all.
ldh a, [hMapObjectIndex]
push af
ldh a, [hObjectStructIndex]
push af
xor a
ldh [hMapObjectIndex], a
ld a, 6
ldh [hObjectStructIndex], a
.loop
push hl
hlcoord 18, 17
call BlinkCursor
pop hl
call JoyTextDelay
ldh a, [hJoyLast]
and A_BUTTON | B_BUTTON
jr z, .loop
pop af
ldh [hObjectStructIndex], a
pop af
ldh [hMapObjectIndex], a
ret
SimpleWaitPressAorB::
.loop
call JoyTextDelay
ldh a, [hJoyLast]
and A_BUTTON | B_BUTTON
jr z, .loop
ret
PromptButton::
; Show a blinking cursor in the lower right-hand
; corner of a textbox and wait until A or B is
; pressed, afterwards, play a sound.
ld a, [wLinkMode]
and a
jr nz, .link
call .wait_input
push de
ld de, SFX_READ_TEXT_2
call PlaySFX
pop de
ret
.link
ld c, 65
jp DelayFrames
.wait_input
ldh a, [hOAMUpdate]
push af
ld a, $1
ldh [hOAMUpdate], a
ld a, [wInputType]
or a
jr z, .input_wait_loop
farcall _DudeAutoInput_A
.input_wait_loop
call .blink_cursor
call JoyTextDelay
ldh a, [hJoyPressed]
and A_BUTTON | B_BUTTON
jr nz, .received_input
call UpdateTimeAndPals
ld a, $1
ldh [hBGMapMode], a
call DelayFrame
jr .input_wait_loop
.received_input
pop af
ldh [hOAMUpdate], a
ret
.blink_cursor
ldh a, [hVBlankCounter]
and %00010000 ; bit 4, a
jr z, .cursor_off
ld a, "▼"
jr .load_cursor_state
.cursor_off
lda_coord 17, 17
.load_cursor_state
ldcoord_a 18, 17
ret
BlinkCursor::
push bc
ld a, [hl]
ld b, a
ld a, "▼"
cp b
pop bc
jr nz, .place_arrow
ldh a, [hMapObjectIndex]
dec a
ldh [hMapObjectIndex], a
ret nz
ldh a, [hObjectStructIndex]
dec a
ldh [hObjectStructIndex], a
ret nz
ld a, "─"
ld [hl], a
ld a, -1
ldh [hMapObjectIndex], a
ld a, 6
ldh [hObjectStructIndex], a
ret
.place_arrow
ldh a, [hMapObjectIndex]
and a
ret z
dec a
ldh [hMapObjectIndex], a
ret nz
dec a
ldh [hMapObjectIndex], a
ldh a, [hObjectStructIndex]
dec a
ldh [hObjectStructIndex], a
ret nz
ld a, 6
ldh [hObjectStructIndex], a
ld a, "▼"
ld [hl], a
ret

63
home/lcd.asm Normal file
View file

@ -0,0 +1,63 @@
; LCD handling
LCD::
push af
ldh a, [hLCDCPointer]
and a
jr z, .done
; At this point it's assumed we're in BANK(wLYOverrides)!
push bc
ldh a, [rLY]
ld c, a
ld b, HIGH(wLYOverrides)
ld a, [bc]
ld b, a
ldh a, [hLCDCPointer]
ld c, a
ld a, b
ldh [c], a
pop bc
.done
pop af
reti
DisableLCD::
; Turn the LCD off
; Don't need to do anything if the LCD is already off
ldh a, [rLCDC]
bit rLCDC_ENABLE, a
ret z
xor a
ldh [rIF], a
ldh a, [rIE]
ld b, a
; Disable VBlank
res VBLANK, a
ldh [rIE], a
.wait
; Wait until VBlank would normally happen
ldh a, [rLY]
cp LY_VBLANK + 1
jr nz, .wait
ldh a, [rLCDC]
and ~(1 << rLCDC_ENABLE)
ldh [rLCDC], a
xor a
ldh [rIF], a
ld a, b
ldh [rIE], a
ret
EnableLCD::
ldh a, [rLCDC]
set rLCDC_ENABLE, a
ldh [rLCDC], a
ret

2248
home/map.asm Normal file

File diff suppressed because it is too large Load diff

575
home/map_objects.asm Normal file
View file

@ -0,0 +1,575 @@
; Functions handling map objects.
GetSpritePalette::
push hl
push de
push bc
ld c, a
farcall _GetSpritePalette
ld a, c
pop bc
pop de
pop hl
ret
GetSpriteVTile::
push hl
push bc
ld hl, wUsedSprites + 2
ld c, SPRITE_GFX_LIST_CAPACITY - 1
ld b, a
ldh a, [hMapObjectIndex]
cp 0
jr z, .nope
ld a, b
.loop
cp [hl]
jr z, .found
inc hl
inc hl
dec c
jr nz, .loop
ld a, [wUsedSprites + 1]
scf
jr .done
.nope
ld a, [wUsedSprites + 1]
jr .done
.found
inc hl
xor a
ld a, [hl]
.done
pop bc
pop hl
ret
DoesSpriteHaveFacings::
push de
push hl
ld b, a
ldh a, [hROMBank]
push af
ld a, BANK(_DoesSpriteHaveFacings)
rst Bankswitch
ld a, b
call _DoesSpriteHaveFacings
ld c, a
pop de
ld a, d
rst Bankswitch
pop hl
pop de
ret
GetPlayerTile::
ld a, [wPlayerTile]
call GetTileCollision
ld b, a
ret
CheckOnWater::
ld a, [wPlayerTile]
call GetTileCollision
sub WATER_TILE
ret z
and a
ret
GetTileCollision::
; Get the collision type of tile a.
push de
push hl
ld hl, TileCollisionTable
ld e, a
ld d, 0
add hl, de
ldh a, [hROMBank]
push af
ld a, BANK(TileCollisionTable)
rst Bankswitch
ld e, [hl]
pop af
rst Bankswitch
ld a, e
and $f ; lo nybble only
pop hl
pop de
ret
CheckGrassTile::
ld d, a
and $f0
cp HI_NYBBLE_TALL_GRASS
jr z, .check
cp HI_NYBBLE_WATER
jr nz, .nope
.check
ld a, d
and LO_NYBBLE_GRASS
ret z
.nope
scf
ret
CheckSuperTallGrassTile::
cp COLL_LONG_GRASS
ret z
cp COLL_LONG_GRASS_1C
ret
CheckCutTreeTile::
cp COLL_CUT_TREE
ret z
cp COLL_CUT_TREE_1A
ret
CheckHeadbuttTreeTile::
cp COLL_HEADBUTT_TREE
ret z
cp COLL_HEADBUTT_TREE_1D
ret
CheckCounterTile::
cp COLL_COUNTER
ret z
cp COLL_COUNTER_98
ret
CheckPitTile::
cp COLL_PIT
ret z
cp COLL_PIT_68
ret
CheckIceTile::
cp COLL_ICE
ret z
cp COLL_ICE_2B
ret z
scf
ret
CheckWhirlpoolTile::
nop
cp COLL_WHIRLPOOL
ret z
cp COLL_WHIRLPOOL_2C
ret z
scf
ret
CheckWaterfallTile::
cp COLL_WATERFALL
ret z
cp COLL_CURRENT_DOWN
ret
CheckStandingOnEntrance::
ld a, [wPlayerTile]
cp COLL_DOOR
ret z
cp COLL_DOOR_79
ret z
cp COLL_STAIRCASE
ret z
cp COLL_CAVE
ret
GetMapObject::
; Return the location of map object a in bc.
ld hl, wMapObjects
ld bc, MAPOBJECT_LENGTH
call AddNTimes
ld b, h
ld c, l
ret
CheckObjectVisibility::
; Sets carry if the object is not visible on the screen.
ldh [hMapObjectIndex], a
call GetMapObject
ld hl, MAPOBJECT_OBJECT_STRUCT_ID
add hl, bc
ld a, [hl]
cp -1
jr z, .not_visible
ldh [hObjectStructIndex], a
call GetObjectStruct
and a
ret
.not_visible
scf
ret
CheckObjectTime::
ld hl, MAPOBJECT_HOUR_1
add hl, bc
ld a, [hl]
cp -1
jr nz, .check_hour
ld hl, MAPOBJECT_TIMEOFDAY
add hl, bc
ld a, [hl]
cp -1
jr z, .timeofday_always
ld hl, .TimesOfDay
ld a, [wTimeOfDay]
add l
ld l, a
jr nc, .ok
inc h
.ok
ld a, [hl]
ld hl, MAPOBJECT_TIMEOFDAY
add hl, bc
and [hl]
jr nz, .timeofday_always
scf
ret
.timeofday_always
and a
ret
.TimesOfDay:
; entries correspond to TimeOfDay values
db MORN
db DAY
db NITE
.check_hour
ld hl, MAPOBJECT_HOUR_1
add hl, bc
ld d, [hl]
ld hl, MAPOBJECT_HOUR_2
add hl, bc
ld e, [hl]
ld hl, hHours
ld a, d
cp e
ret z
jr c, .check_timeofday
ld a, [hl]
cp d
ret nc
cp e
ret z
ccf
ret
.check_timeofday
ld a, e
cp [hl]
ret c
ld a, [hl]
cp d
ret
UnmaskCopyMapObjectStruct::
ldh [hMapObjectIndex], a
call UnmaskObject
ldh a, [hMapObjectIndex]
call GetMapObject
farcall CopyObjectStruct
ret
ApplyDeletionToMapObject::
ldh [hMapObjectIndex], a
call GetMapObject
ld hl, MAPOBJECT_OBJECT_STRUCT_ID
add hl, bc
ld a, [hl]
cp -1
ret z ; already hidden
ld [hl], -1
push af
call .CheckStopFollow
pop af
call GetObjectStruct
farcall DeleteMapObject
ret
.CheckStopFollow:
ld hl, wObjectFollow_Leader
cp [hl]
jr z, .ok
ld hl, wObjectFollow_Follower
cp [hl]
ret nz
.ok
farcall StopFollow
ld a, -1
ld [wObjectFollow_Leader], a
ld [wObjectFollow_Follower], a
ret
DeleteObjectStruct::
call ApplyDeletionToMapObject
jp MaskObject
CopyPlayerObjectTemplate::
push hl
call GetMapObject
ld d, b
ld e, c
ld a, -1
ld [de], a
inc de
pop hl
ld bc, MAPOBJECT_LENGTH - 1
jp CopyBytes
LoadMovementDataPointer::
; Load the movement data pointer for object a.
ld [wMovementObject], a
ldh a, [hROMBank]
ld [wMovementDataBank], a
ld a, l
ld [wMovementDataAddress], a
ld a, h
ld [wMovementDataAddress + 1], a
ld a, [wMovementObject]
call CheckObjectVisibility
ret c
ld hl, OBJECT_MOVEMENT_TYPE
add hl, bc
ld [hl], SPRITEMOVEDATA_SCRIPTED
ld hl, OBJECT_STEP_TYPE
add hl, bc
ld [hl], STEP_TYPE_RESET
ld hl, wVramState
set 7, [hl]
and a
ret
FindFirstEmptyObjectStruct::
; Returns the index of the first empty object struct in A and its address in HL, then sets carry.
; If all object structs are occupied, A = 0 and Z is set.
; Preserves BC and DE.
push bc
push de
ld hl, wObjectStructs
ld de, OBJECT_LENGTH
ld c, NUM_OBJECT_STRUCTS
.loop
ld a, [hl]
and a
jr z, .break
add hl, de
dec c
jr nz, .loop
xor a
jr .done
.break
ld a, NUM_OBJECT_STRUCTS
sub c
scf
.done
pop de
pop bc
ret
GetSpriteMovementFunction::
ld hl, OBJECT_MOVEMENT_TYPE
add hl, bc
ld a, [hl]
cp NUM_SPRITEMOVEDATA
jr c, .ok
xor a
.ok
ld hl, SpriteMovementData + SPRITEMOVEATTR_MOVEMENT
ld e, a
ld d, 0
rept NUM_SPRITEMOVEDATA_FIELDS
add hl, de
endr
ld a, [hl]
ret
GetInitialFacing::
push bc
push de
ld e, a
ld d, 0
ld hl, SpriteMovementData + SPRITEMOVEATTR_FACING
rept NUM_SPRITEMOVEDATA_FIELDS
add hl, de
endr
ld a, BANK(SpriteMovementData)
call GetFarByte
add a
add a
maskbits NUM_DIRECTIONS, 2
pop de
pop bc
ret
CopySpriteMovementData::
ld l, a
ldh a, [hROMBank]
push af
ld a, BANK(SpriteMovementData)
rst Bankswitch
ld a, l
push bc
call .CopyData
pop bc
pop af
rst Bankswitch
ret
.CopyData:
ld hl, OBJECT_MOVEMENT_TYPE
add hl, de
ld [hl], a
push de
ld e, a
ld d, 0
ld hl, SpriteMovementData + SPRITEMOVEATTR_FACING
rept NUM_SPRITEMOVEDATA_FIELDS
add hl, de
endr
ld b, h
ld c, l
pop de
ld a, [bc]
inc bc
rlca
rlca
maskbits NUM_DIRECTIONS, 2
ld hl, OBJECT_DIRECTION
add hl, de
ld [hl], a
ld a, [bc]
inc bc
ld hl, OBJECT_ACTION
add hl, de
ld [hl], a
ld a, [bc]
inc bc
ld hl, OBJECT_FLAGS1
add hl, de
ld [hl], a
ld a, [bc]
inc bc
ld hl, OBJECT_FLAGS2
add hl, de
ld [hl], a
ld a, [bc]
inc bc
ld hl, OBJECT_PALETTE
add hl, de
ld [hl], a
ret
_GetMovementIndex::
; Switch to the movement data bank
ldh a, [hROMBank]
push af
ld a, [hli]
rst Bankswitch
; Load the current script byte as given by OBJECT_MOVEMENT_INDEX, and increment OBJECT_MOVEMENT_INDEX
ld a, [hli]
ld d, [hl]
ld hl, OBJECT_MOVEMENT_INDEX
add hl, bc
add [hl]
ld e, a
ld a, d
adc 0
ld d, a
inc [hl]
ld a, [de]
ld h, a
pop af
rst Bankswitch
ld a, h
ret
SetVramState_Bit0:: ; unreferenced
ld hl, wVramState
set 0, [hl]
ret
ResetVramState_Bit0:: ; unreferenced
ld hl, wVramState
res 0, [hl]
ret
UpdateSprites::
ld a, [wVramState]
bit 0, a
ret z
farcall UpdateAllObjectsFrozen
farcall _UpdateSprites
ret
GetObjectStruct::
ld bc, OBJECT_LENGTH
ld hl, wObjectStructs
call AddNTimes
ld b, h
ld c, l
ret
DoesObjectHaveASprite::
ld hl, OBJECT_SPRITE
add hl, bc
ld a, [hl]
and a
ret
SetSpriteDirection::
; preserves other flags
push af
ld hl, OBJECT_DIRECTION
add hl, bc
ld a, [hl]
and %11110011
ld e, a
pop af
maskbits NUM_DIRECTIONS, 2
or e
ld [hl], a
ret
GetSpriteDirection::
ld hl, OBJECT_DIRECTION
add hl, bc
ld a, [hl]
maskbits NUM_DIRECTIONS, 2
ret

58
home/math.asm Normal file
View file

@ -0,0 +1,58 @@
SimpleMultiply::
; Return a * c.
and a
ret z
push bc
ld b, a
xor a
.loop
add c
dec b
jr nz, .loop
pop bc
ret
SimpleDivide::
; Divide a by c. Return quotient b and remainder a.
ld b, 0
.loop
inc b
sub c
jr nc, .loop
dec b
add c
ret
Multiply::
; Multiply hMultiplicand (3 bytes) by hMultiplier. Result in hProduct.
; All values are big endian.
push hl
push bc
callfar _Multiply
pop bc
pop hl
ret
Divide::
; Divide hDividend length b (max 4 bytes) by hDivisor. Result in hQuotient.
; All values are big endian.
push hl
push de
push bc
homecall _Divide
pop bc
pop de
pop hl
ret
SubtractAbsolute:: ; unreferenced
; Return |a - b|, sign in carry.
sub b
ret nc
cpl
add 1
scf
ret

813
home/menu.asm Normal file
View file

@ -0,0 +1,813 @@
Load2DMenuData::
push hl
push bc
ld hl, w2DMenuData
ld b, w2DMenuDataEnd - w2DMenuData
.loop
ld a, [de]
inc de
ld [hli], a
dec b
jr nz, .loop
; Reset menu state
ld a, $1
ld [hli], a ; wMenuCursorY
ld [hli], a ; wMenuCursorX
xor a
ld [hli], a ; wCursorOffCharacter
ld [hli], a ; wCursorCurrentTile
ld [hli], a
pop bc
pop hl
ret
StaticMenuJoypad::
callfar _StaticMenuJoypad
jr GetMenuJoypad
ScrollingMenuJoypad::
callfar _ScrollingMenuJoypad
; fallthrough
GetMenuJoypad::
push bc
push af
ldh a, [hJoyLast]
and D_PAD
ld b, a
ldh a, [hJoyPressed]
and BUTTONS
or b
ld b, a
pop af
ld a, b
pop bc
ret
PlaceHollowCursor::
ld hl, wCursorCurrentTile
ld a, [hli]
ld h, [hl]
ld l, a
ld [hl], "▷"
ret
HideCursor::
ld hl, wCursorCurrentTile
ld a, [hli]
ld h, [hl]
ld l, a
ld [hl], " "
ret
PushWindow::
callfar _PushWindow
ret
ExitMenu::
push af
callfar _ExitMenu
pop af
ret
InitVerticalMenuCursor::
callfar _InitVerticalMenuCursor
ret
CloseWindow::
push af
call ExitMenu
call ApplyTilemap
call UpdateSprites
pop af
ret
RestoreTileBackup::
call MenuBoxCoord2Tile
call .copy
call MenuBoxCoord2Attr
.copy
call GetMenuBoxDims
inc b
inc c
.row
push bc
push hl
.col
ld a, [de]
ld [hli], a
dec de
dec c
jr nz, .col
pop hl
ld bc, SCREEN_WIDTH
add hl, bc
pop bc
dec b
jr nz, .row
ret
PopWindow::
ld b, wMenuHeaderEnd - wMenuHeader
ld de, wMenuHeader
.loop
ld a, [hld]
ld [de], a
inc de
dec b
jr nz, .loop
ret
GetMenuBoxDims::
ld a, [wMenuBorderTopCoord] ; top
ld b, a
ld a, [wMenuBorderBottomCoord] ; bottom
sub b
ld b, a
ld a, [wMenuBorderLeftCoord] ; left
ld c, a
ld a, [wMenuBorderRightCoord] ; right
sub c
ld c, a
ret
CopyMenuData::
push hl
push de
push bc
push af
ld hl, wMenuDataPointer
ld a, [hli]
ld h, [hl]
ld l, a
ld de, wMenuData
ld bc, wMenuDataEnd - wMenuData
call CopyBytes
pop af
pop bc
pop de
pop hl
ret
GetWindowStackTop::
ld hl, wWindowStackPointer
ld a, [hli]
ld h, [hl]
ld l, a
inc hl
ld a, [hli]
ld h, [hl]
ld l, a
ret
PlaceVerticalMenuItems::
call CopyMenuData
ld hl, wMenuDataPointer
ld e, [hl]
inc hl
ld d, [hl]
call GetMenuTextStartCoord
call Coord2Tile ; hl now contains the tilemap address where we will start printing text.
inc de
ld a, [de] ; Number of items
inc de
ld b, a
.loop
push bc
call PlaceString
inc de
ld bc, 2 * SCREEN_WIDTH
add hl, bc
pop bc
dec b
jr nz, .loop
ld a, [wMenuDataFlags]
bit 4, a
ret z
call MenuBoxCoord2Tile
ld a, [de]
ld c, a
inc de
ld b, 0
add hl, bc
jp PlaceString
MenuBox::
call MenuBoxCoord2Tile
call GetMenuBoxDims
dec b
dec c
jp Textbox
GetMenuTextStartCoord::
ld a, [wMenuBorderTopCoord]
ld b, a
inc b
ld a, [wMenuBorderLeftCoord]
ld c, a
inc c
; bit 6: if not set, leave extra room on top
ld a, [wMenuDataFlags]
bit 6, a
jr nz, .bit_6_set
inc b
.bit_6_set
; bit 7: if set, leave extra room on the left
ld a, [wMenuDataFlags]
bit 7, a
ret z
inc c
ret
ClearMenuBoxInterior::
call MenuBoxCoord2Tile
ld bc, SCREEN_WIDTH + 1
add hl, bc
call GetMenuBoxDims
dec b
dec c
jp ClearBox
ClearWholeMenuBox::
call MenuBoxCoord2Tile
call GetMenuBoxDims
inc c
inc b
jp ClearBox
MenuBoxCoord2Tile::
ld a, [wMenuBorderLeftCoord]
ld c, a
ld a, [wMenuBorderTopCoord]
ld b, a
; fallthrough
Coord2Tile::
; Return the address of wTilemap(c, b) in hl.
xor a
ld h, a
ld l, b
ld a, c
ld b, h
ld c, l
add hl, hl
add hl, hl
add hl, bc
add hl, hl
add hl, hl
ld c, a
xor a
ld b, a
add hl, bc
bccoord 0, 0
add hl, bc
ret
MenuBoxCoord2Attr::
ld a, [wMenuBorderLeftCoord]
ld c, a
ld a, [wMenuBorderTopCoord]
ld b, a
; fallthrough
Coord2Attr:: ; unreferenced
; Return the address of wAttrmap(c, b) in hl.
xor a
ld h, a
ld l, b
ld a, c
ld b, h
ld c, l
add hl, hl
add hl, hl
add hl, bc
add hl, hl
add hl, hl
ld c, a
xor a
ld b, a
add hl, bc
bccoord 0, 0, wAttrmap
add hl, bc
ret
LoadMenuHeader::
call CopyMenuHeader
jp PushWindow
CopyMenuHeader::
ld de, wMenuHeader
ld bc, wMenuHeaderEnd - wMenuHeader
call CopyBytes
ldh a, [hROMBank]
ld [wMenuDataBank], a
ret
StoreMenuCursorPosition::
ld [wMenuCursorPosition], a
ret
MenuTextbox::
push hl
call LoadMenuTextbox
pop hl
jp PrintText
LoadMenuTextbox::
ld hl, .MenuHeader
jp LoadMenuHeader
.MenuHeader:
db MENU_BACKUP_TILES ; flags
menu_coords 0, 12, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1
dw vTiles0
db 0 ; default option
MenuTextboxBackup::
call MenuTextbox
jp CloseWindow
LoadStandardMenuHeader::
ld hl, .MenuHeader
jp LoadMenuHeader
.MenuHeader:
db MENU_BACKUP_TILES ; flags
menu_coords 0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1
dw 0
db 1 ; default option
VerticalMenu::
xor a
ldh [hBGMapMode], a
call MenuBox
call UpdateSprites
call PlaceVerticalMenuItems
call ApplyTilemap
call CopyMenuData
ld a, [wMenuDataFlags]
bit 7, a
jr z, .cancel
call InitVerticalMenuCursor
call StaticMenuJoypad
call MenuClickSound
bit 1, a
jr z, .okay
.cancel
scf
ret
.okay
and a
ret
GetMenu2::
call LoadMenuHeader
call VerticalMenu
call CloseWindow
ld a, [wMenuCursorY]
ret
CopyNameFromMenu::
push hl
push bc
push af
ld hl, wMenuDataPointer
ld a, [hli]
ld h, [hl]
ld l, a
inc hl
inc hl
pop af
call GetNthString
ld d, h
ld e, l
call CopyName1
pop bc
pop hl
ret
PlaceGenericTwoOptionBox:: ; unreferenced
call LoadMenuHeader
jr InterpretTwoOptionMenu
YesNoBox::
lb bc, SCREEN_WIDTH - 6, 7
; fallthrough
PlaceYesNoBox::
; Return nc (yes) or c (no).
push bc
ld hl, YesNoMenuHeader
call CopyMenuHeader
pop bc
; This seems to be an overflow prevention,
; but it was coded wrong.
ld a, b
cp SCREEN_WIDTH - 1 - 5
jr nz, .okay ; should this be "jr nc"?
ld a, SCREEN_WIDTH - 1 - 5
ld b, a
.okay
ld a, b
ld [wMenuBorderLeftCoord], a
add 5
ld [wMenuBorderRightCoord], a
ld a, c
ld [wMenuBorderTopCoord], a
add 4
ld [wMenuBorderBottomCoord], a
call PushWindow
; fallthrough
InterpretTwoOptionMenu::
call VerticalMenu
push af
ld c, $f
call DelayFrames
call CloseWindow
pop af
jr c, .no
ld a, [wMenuCursorY]
cp 2 ; no
jr z, .no
and a
ret
.no
ld a, 2
ld [wMenuCursorY], a
scf
ret
YesNoMenuHeader::
db MENU_BACKUP_TILES ; flags
menu_coords 10, 5, 15, 9
dw .MenuData
db 1 ; default option
.MenuData:
db STATICMENU_CURSOR | STATICMENU_NO_TOP_SPACING ; flags
db 2
db "YES@"
db "NO@"
OffsetMenuHeader::
call _OffsetMenuHeader
jp PushWindow
_OffsetMenuHeader::
push de
call CopyMenuHeader
pop de
ld a, [wMenuBorderLeftCoord]
ld h, a
ld a, [wMenuBorderRightCoord]
sub h
ld h, a
ld a, d
ld [wMenuBorderLeftCoord], a
add h
ld [wMenuBorderRightCoord], a
ld a, [wMenuBorderTopCoord]
ld l, a
ld a, [wMenuBorderBottomCoord]
sub l
ld l, a
ld a, e
ld [wMenuBorderTopCoord], a
add l
ld [wMenuBorderBottomCoord], a
ret
DoNthMenu::
call DrawVariableLengthMenuBox
call MenuWriteText
call InitMenuCursorAndButtonPermissions
call GetStaticMenuJoypad
call GetMenuJoypad
jp MenuClickSound
SetUpMenu::
call DrawVariableLengthMenuBox
call MenuWriteText
call InitMenuCursorAndButtonPermissions
ld hl, w2DMenuFlags1
set 7, [hl]
ret
DrawVariableLengthMenuBox::
call CopyMenuData
call GetMenuIndexSet
call AutomaticGetMenuBottomCoord
jp MenuBox
MenuWriteText::
xor a
ldh [hBGMapMode], a
call GetMenuIndexSet ; sort out the text
call RunMenuItemPrintingFunction ; actually write it
call SafeUpdateSprites
ldh a, [hOAMUpdate]
push af
ld a, $1
ldh [hOAMUpdate], a
call ApplyTilemap
pop af
ldh [hOAMUpdate], a
ret
AutomaticGetMenuBottomCoord::
ld a, [wMenuBorderLeftCoord]
ld c, a
ld a, [wMenuBorderRightCoord]
sub c
ld c, a
ld a, [wMenuDataItems]
add a
inc a
ld b, a
ld a, [wMenuBorderTopCoord]
add b
ld [wMenuBorderBottomCoord], a
ret
GetMenuIndexSet::
ld hl, wMenuDataIndicesPointer
ld a, [hli]
ld h, [hl]
ld l, a
ld a, [wWhichIndexSet]
and a
jr z, .skip
ld b, a
ld c, -1
.loop
ld a, [hli]
cp c
jr nz, .loop
dec b
jr nz, .loop
.skip
ld d, h
ld e, l
ld a, [hl]
ld [wMenuDataItems], a
ret
RunMenuItemPrintingFunction::
call MenuBoxCoord2Tile
ld bc, 2 * SCREEN_WIDTH + 2
add hl, bc
.loop
inc de
ld a, [de]
cp -1
ret z
ld [wMenuSelection], a
push de
push hl
ld d, h
ld e, l
ld hl, wMenuDataDisplayFunctionPointer
call ._hl_
pop hl
ld de, 2 * SCREEN_WIDTH
add hl, de
pop de
jr .loop
._hl_
ld a, [hli]
ld h, [hl]
ld l, a
jp hl
InitMenuCursorAndButtonPermissions::
call InitVerticalMenuCursor
ld hl, wMenuJoypadFilter
ld a, [wMenuDataFlags]
bit 3, a
jr z, .disallow_select
set START_F, [hl]
.disallow_select
ld a, [wMenuDataFlags]
bit 2, a
ret z
set D_LEFT_F, [hl]
set D_RIGHT_F, [hl]
ret
GetScrollingMenuJoypad::
call ScrollingMenuJoypad
ld hl, wMenuJoypadFilter
and [hl]
jr ContinueGettingMenuJoypad
GetStaticMenuJoypad::
xor a
ld [wMenuJoypad], a
call StaticMenuJoypad
; fallthrough
ContinueGettingMenuJoypad:
bit A_BUTTON_F, a
jr nz, .a_button
bit B_BUTTON_F, a
jr nz, .b_start
bit START_F, a
jr nz, .b_start
bit D_RIGHT_F, a
jr nz, .d_right
bit D_LEFT_F, a
jr nz, .d_left
xor a
ld [wMenuJoypad], a
jr .done
.d_right
ld a, D_RIGHT
ld [wMenuJoypad], a
jr .done
.d_left
ld a, D_LEFT
ld [wMenuJoypad], a
jr .done
.a_button
ld a, A_BUTTON
ld [wMenuJoypad], a
.done
call GetMenuIndexSet
ld a, [wMenuCursorY]
ld l, a
ld h, 0
add hl, de
ld a, [hl]
ld [wMenuSelection], a
ld a, [wMenuCursorY]
ld [wMenuCursorPosition], a
and a
ret
.b_start
ld a, B_BUTTON
ld [wMenuJoypad], a
ld a, -1
ld [wMenuSelection], a
scf
ret
PlaceMenuStrings::
push de
ld hl, wMenuDataPointerTableAddr
ld a, [hli]
ld h, [hl]
ld l, a
ld a, [wMenuSelection]
call GetNthString
ld d, h
ld e, l
pop hl
jp PlaceString
PlaceNthMenuStrings::
push de
ld a, [wMenuSelection]
call GetMenuDataPointerTableEntry
inc hl
inc hl
ld a, [hli]
ld d, [hl]
ld e, a
pop hl
jp PlaceString
MenuJumptable::
ld a, [wMenuSelection]
call GetMenuDataPointerTableEntry
ld a, [hli]
ld h, [hl]
ld l, a
jp hl
GetMenuDataPointerTableEntry::
ld e, a
ld d, 0
ld hl, wMenuDataPointerTableAddr
ld a, [hli]
ld h, [hl]
ld l, a
add hl, de
add hl, de
add hl, de
add hl, de
ret
ClearWindowData::
ld hl, wMenuMetadata
call .ClearMenuData
ld hl, wMenuHeader
call .ClearMenuData
ld hl, wMenuData
call .ClearMenuData
ld hl, wMoreMenuData
call .ClearMenuData
ldh a, [rSVBK]
push af
ld a, BANK(wWindowStack)
ldh [rSVBK], a
xor a
ld hl, wWindowStackBottom
ld [hld], a
ld [hld], a
ld a, l
ld [wWindowStackPointer], a
ld a, h
ld [wWindowStackPointer + 1], a
pop af
ldh [rSVBK], a
ret
.ClearMenuData:
ld bc, wMenuMetadataEnd - wMenuMetadata
assert wMenuMetadataEnd - wMenuMetadata == wMenuHeaderEnd - wMenuHeader
assert wMenuMetadataEnd - wMenuMetadata == wMenuDataEnd - wMenuData
assert wMenuMetadataEnd - wMenuMetadata == wMoreMenuDataEnd - wMoreMenuData
xor a
jp ByteFill
MenuClickSound::
push af
and A_BUTTON | B_BUTTON
jr z, .nosound
ld hl, wMenuFlags
bit 3, [hl]
jr nz, .nosound
call PlayClickSFX
.nosound
pop af
ret
PlayClickSFX::
push de
ld de, SFX_READ_TEXT_2
call PlaySFX
pop de
ret
MenuTextboxWaitButton::
call MenuTextbox
call WaitButton
jp ExitMenu
Place2DMenuItemName::
ldh [hTempBank], a
ldh a, [hROMBank]
push af
ldh a, [hTempBank]
rst Bankswitch
call PlaceString
pop af
rst Bankswitch
ret
_2DMenu::
ldh a, [hROMBank]
ld [wMenuData_2DMenuItemStringsBank], a
farcall _2DMenu_
ld a, [wMenuCursorPosition]
ret
InterpretBattleMenu::
ldh a, [hROMBank]
ld [wMenuData_2DMenuItemStringsBank], a
farcall _InterpretBattleMenu
ld a, [wMenuCursorPosition]
ret
InterpretMobileMenu:: ; unreferenced
ldh a, [hROMBank]
ld [wMenuData_2DMenuItemStringsBank], a
farcall _InterpretMobileMenu
ld a, [wMenuCursorPosition]
ret

255
home/mobile.asm Normal file
View file

@ -0,0 +1,255 @@
MobileAPI::
; Mobile
cp $2
ld [wMobileAPIIndex], a
ld a, l
ld [wc986], a
ld a, h
ld [wc987], a
jr nz, .okay
ld [wc982], a
ld a, l
ld [wc981], a
ld hl, wc983
ld a, c
ld [hli], a
ld a, b
ld [hl], a
.okay
ld hl, wc822
set 6, [hl]
ldh a, [hROMBank]
push af
ld a, BANK(_MobileAPI)
ld [wc981], a
rst Bankswitch
jp _MobileAPI
ReturnMobileAPI::
; Return from _MobileAPI
ld [wc986], a
ld a, l
ld [wc987], a
ld a, h
ld [wMobileAPIIndex], a
pop bc
ld a, b
ld [wc981], a
rst Bankswitch
ld hl, wc822
res 6, [hl]
ld hl, wc987
ld a, [hli]
ld h, [hl]
ld l, a
ld a, [wc986]
ret
MobileReceive::
ldh a, [hROMBank]
push af
ld a, BANK(_MobileReceive)
ld [wc981], a
rst Bankswitch
call _MobileReceive
pop bc
ld a, b
ld [wc981], a
rst Bankswitch
ret
MobileTimer::
push af
push bc
push de
push hl
ldh a, [hMobile]
and a
jr z, .pop_ret
xor a
ldh [rTAC], a
; Turn off timer interrupt
ldh a, [rIF]
and 1 << VBLANK | 1 << LCD_STAT | 1 << SERIAL | 1 << JOYPAD
ldh [rIF], a
ld a, [wc86a]
or a
jr z, .pop_ret
ld a, [wc822]
bit 1, a
jr nz, .skip_timer
ldh a, [rSC]
and 1 << rSC_ON
jr nz, .skip_timer
ldh a, [hROMBank]
push af
ld a, BANK(_Timer)
ld [wc981], a
rst Bankswitch
call _Timer
pop bc
ld a, b
ld [wc981], a
rst Bankswitch
.skip_timer
ldh a, [rTMA]
ldh [rTIMA], a
ld a, 1 << rTAC_ON | rTAC_65536_HZ
ldh [rTAC], a
.pop_ret
pop hl
pop de
pop bc
pop af
reti
Function3eea::
push hl
push bc
ld de, wAttrmap - wTilemap
add hl, de
inc b
inc b
inc c
inc c
call Function3f35
pop bc
pop hl
jp MobileHome_PlaceBox
Function3f20::
hlcoord 0, 0, wAttrmap
ld b, 6
ld c, 20
call Function3f35
hlcoord 0, 0
ld b, 4
ld c, 18
jp MobileHome_PlaceBox
Function3f35::
ld a, 6
ld de, SCREEN_WIDTH
.row
push bc
push hl
.col
ld [hli], a
dec c
jr nz, .col
pop hl
add hl, de
pop bc
dec b
jr nz, .row
ret
MobileHome_PlaceBox:
push bc
call .FillTop
pop bc
.RowLoop:
push bc
call .FillMiddle
pop bc
dec b
jr nz, .RowLoop
call .FillBottom
ret
.FillTop:
ld a, $63
ld d, $62
ld e, $64
jr .FillRow
.FillBottom:
ld a, $68
ld d, $67
ld e, $69
jr .FillRow
.FillMiddle:
ld a, $7f
ld d, $65
ld e, $66
.FillRow:
push hl
ld [hl], d
inc hl
.FillLoop:
ld [hli], a
dec c
jr nz, .FillLoop
ld [hl], e
pop hl
ld de, SCREEN_WIDTH
add hl, de
ret
Function3f7c::
call MenuBoxCoord2Tile
call GetMenuBoxDims
dec b
dec c
jp Function3eea
Function3f88::
ld hl, wDecompressScratch
ld b, 0
.row
push bc
ld c, 1 tiles / 2
.col
ld a, [de]
inc de
cpl
ld [hl], 0
inc hl
ld [hli], a
dec c
jr nz, .col
pop bc
dec c
jr nz, .row
ret
Function3f9f::
ld hl, wDecompressScratch
.row
push bc
ld c, 1 tiles / 2
.col
ld a, [de]
inc de
inc de
cpl
ld [hl], $0
inc hl
ld [hli], a
dec c
jr nz, .col
pop bc
dec c
jr nz, .row
ret

128
home/movement.asm Normal file
View file

@ -0,0 +1,128 @@
InitMovementBuffer::
ld [wMovementBufferObject], a
xor a
ld [wMovementBufferCount], a
ld [wUnusedMovementBufferBank], a
ld a, LOW(wMovementBuffer)
ld [wUnusedMovementBufferPointer], a
ld a, HIGH(wMovementBuffer)
ld [wUnusedMovementBufferPointer + 1], a
ret
DecrementMovementBufferCount::
ld a, [wMovementBufferCount]
and a
ret z
dec a
ld [wMovementBufferCount], a
ret
AppendToMovementBuffer::
push hl
push de
ld hl, wMovementBufferCount
ld e, [hl]
inc [hl]
ld d, 0
ld hl, wMovementBuffer
add hl, de
ld [hl], a
pop de
pop hl
ret
AppendToMovementBufferNTimes::
push af
ld a, c
and a
jr nz, .okay
pop af
ret
.okay
pop af
.loop
call AppendToMovementBuffer
dec c
jr nz, .loop
ret
ComputePathToWalkToPlayer::
push af
; compare x coords, load left/right into h, and x distance into d
ld a, b
sub d
ld h, LEFT
jr nc, .got_x_distance
dec a
cpl
ld h, RIGHT
.got_x_distance
ld d, a
; compare y coords, load up/down into l, and y distance into e
ld a, c
sub e
ld l, UP
jr nc, .got_y_distance
dec a
cpl
ld l, DOWN
.got_y_distance
ld e, a
; if the x distance is less than the y distance, swap h and l, and swap d and e
cp d
jr nc, .done
ld a, h
ld h, l
ld l, a
ld a, d
ld d, e
ld e, a
.done
pop af
ld b, a
; Add movement in the longer direction first...
ld a, h
call .GetMovementData
ld c, d
call AppendToMovementBufferNTimes
; ... then add the shorter direction.
ld a, l
call .GetMovementData
ld c, e
call AppendToMovementBufferNTimes
ret
.GetMovementData:
push de
push hl
ld l, b
ld h, 0
add hl, hl
add hl, hl
ld e, a
ld d, 0
add hl, de
ld de, .MovementData
add hl, de
ld a, [hl]
pop hl
pop de
ret
.MovementData:
slow_step DOWN
slow_step UP
slow_step LEFT
slow_step RIGHT
step DOWN
step UP
step LEFT
step RIGHT
big_step DOWN
big_step UP
big_step LEFT
big_step RIGHT

36
home/moves.asm Normal file
View file

@ -0,0 +1,36 @@
GetMoveAttribute::
; Return attribute a of move l in a; clobbers hl.
; Replaces the old GetMoveAttr (renamed to avoid confusion).
sub 1
push bc
ld c, a
ld a, l
jr c, .done
call GetMoveAddress
ld b, 0
add hl, bc
call GetFarByte
.done
pop bc
ret
GetMoveAddress::
; Get the far address for move a's attributes in a:hl.
; This structure will not contain the animation byte! All MOVE_* constants must be reduced by 1 when indexing.
push bc
call GetMoveIndexFromID
ld b, h
ld c, l
ld hl, Moves
ld a, BANK(Moves)
call LoadIndirectPointer
pop bc
ret
GetMoveData::
; Copy move struct a to de.
ld [de], a
inc de
call GetMoveAddress
ld bc, MOVE_LENGTH - 1
jp FarCopyBytes

284
home/names.asm Normal file
View file

@ -0,0 +1,284 @@
NamesPointers::
; entries correspond to GetName constants (see constants/text_constants.asm); MON_NAME and MOVE_NAME are not handled by this table
dba ItemNames ; ITEM_NAME
dbw 0, wPartyMonOTs ; PARTY_OT_NAME
dbw 0, wOTPartyMonOTs ; ENEMY_OT_NAME
dba TrainerClassNames ; TRAINER_NAME
GetName::
; Return name wCurSpecies from name list wNamedObjectType in wStringBuffer1.
ldh a, [hROMBank]
push af
push hl
push bc
push de
ld a, [wCurSpecies]
ld [wNamedObjectIndex], a
ld a, [wNamedObjectType]
dec a ; MON_NAME
ld hl, GetPokemonName
jr z, .go
dec a ; MOVE_NAME
ld hl, GetMoveName
jr z, .go
dec a
ld hl, .generic_function
.go
call _hl_
pop de
pop bc
pop hl
pop af
rst Bankswitch
ret
.generic_function
ld l, a
add a, a
add a, l
add a, LOW(NamesPointers)
ld l, a
ld a, HIGH(NamesPointers)
adc 0
ld h, a
ld a, [hli]
rst Bankswitch
ld a, [hli]
ld h, [hl]
ld l, a
ld a, [wCurSpecies]
dec a
call GetNthString
ld de, wStringBuffer1
ld bc, ITEM_NAME_LENGTH
jp CopyBytes
GetNthString16::
; Like GetNthString, but with a 16-bit index in bc
inc b
jr .handle_loop
.loop
xor a
call GetNthString.loop ; will act as a = $100
.handle_loop
dec b
jr nz, .loop
ld a, c
; fallthrough
GetNthString::
; Return the address of the
; ath string starting from hl.
and a
ret z
.loop
push bc
ld b, a
ld c, "@"
.readChar
ld a, [hli]
cp c
jr nz, .readChar
dec b
jr nz, .readChar
pop bc
ret
GetBasePokemonName::
; Discards gender (Nidoran).
push hl
call GetPokemonName
ld hl, wStringBuffer1
.loop
ld a, [hl]
cp "@"
jr z, .quit
cp "♂"
jr z, .end
cp "♀"
jr z, .end
inc hl
jr .loop
.end
ld [hl], "@"
.quit
pop hl
ret
GetPokemonName::
; Get Pokemon name for wNamedObjectIndex.
ldh a, [hROMBank]
push af
push hl
ld a, BANK(PokemonNames)
rst Bankswitch
; Each name is ten characters
ld a, [wNamedObjectIndex]
call GetPokemonIndexFromID
ld e, l
ld d, h
add hl, hl
add hl, hl
add hl, de
add hl, hl
ld de, PokemonNames - 10
add hl, de
; Terminator
ld de, wStringBuffer1
push de
ld bc, MON_NAME_LENGTH - 1
call CopyBytes
ld hl, wStringBuffer1 + MON_NAME_LENGTH - 1
ld [hl], "@"
pop de
pop hl
pop af
rst Bankswitch
ret
GetItemName::
; Get item name for wNamedObjectIndex.
push hl
push bc
ld a, [wNamedObjectIndex]
cp TM01
jr nc, .TM
ld [wCurSpecies], a
ld a, ITEM_NAME
ld [wNamedObjectType], a
call GetName
jr .Copied
.TM:
call GetTMHMName
.Copied:
ld de, wStringBuffer1
pop bc
pop hl
ret
GetTMHMName::
; Get TM/HM name for item wNamedObjectIndex.
push hl
push de
push bc
ld a, [wNamedObjectIndex]
push af
; TM/HM prefix
cp HM01
push af
jr c, .TM
ld hl, .HMText
ld bc, .HMTextEnd - .HMText
jr .copy
.TM:
ld hl, .TMText
ld bc, .TMTextEnd - .TMText
.copy
ld de, wStringBuffer1
call CopyBytes
; TM/HM number
push de
ld a, [wNamedObjectIndex]
ld c, a
callfar GetTMHMNumber
pop de
; HM numbers start from 51, not 1
pop af
ld a, c
jr c, .not_hm
sub NUM_TMS
.not_hm
; Divide and mod by 10 to get the top and bottom digits respectively
ld b, "0"
.mod10
sub 10
jr c, .done_mod
inc b
jr .mod10
.done_mod
add 10
push af
ld a, b
ld [de], a
inc de
pop af
ld b, "0"
add b
ld [de], a
; End the string
inc de
ld a, "@"
ld [de], a
pop af
ld [wNamedObjectIndex], a
pop bc
pop de
pop hl
ret
.TMText:
db "TM"
.TMTextEnd:
db "@"
.HMText:
db "HM"
.HMTextEnd:
db "@"
INCLUDE "home/hm_moves.asm"
GetMoveName::
push hl
push bc
ldh a, [hROMBank]
push af
ld a, BANK(MoveNames)
rst Bankswitch
ld a, [wNamedObjectIndex]
call GetMoveIndexFromID
dec hl
ld b, h
ld c, l
ld hl, MoveNames
call GetNthString16
ld de, wStringBuffer1
push de
ld bc, MOVE_NAME_LENGTH
call CopyBytes
pop de
pop af
rst Bankswitch
pop bc
pop hl
ret

329
home/palettes.asm Normal file
View file

@ -0,0 +1,329 @@
; Functions dealing with palettes.
UpdatePalsIfCGB::
; update bgp data from wBGPals2
; update obp data from wOBPals2
; return carry if successful
; check cgb
ldh a, [hCGB]
and a
ret z
UpdateCGBPals::
; return carry if successful
; any pals to update?
ldh a, [hCGBPalUpdate]
and a
ret z
; fallthrough
ForceUpdateCGBPals::
ldh a, [rSVBK]
push af
ld a, BANK(wBGPals2)
ldh [rSVBK], a
ld hl, wBGPals2
; copy 8 pals to bgpd
ld a, 1 << rBGPI_AUTO_INCREMENT
ldh [rBGPI], a
ld c, LOW(rBGPD)
ld b, 8 / 2
.bgp
rept (1 palettes) * 2
ld a, [hli]
ldh [c], a
endr
dec b
jr nz, .bgp
; hl is now wOBPals2
; copy 8 pals to obpd
ld a, 1 << rOBPI_AUTO_INCREMENT
ldh [rOBPI], a
ld c, LOW(rOBPD)
ld b, 8 / 2
.obp
rept (1 palettes) * 2
ld a, [hli]
ldh [c], a
endr
dec b
jr nz, .obp
pop af
ldh [rSVBK], a
; clear pal update queue
xor a
ldh [hCGBPalUpdate], a
scf
ret
DmgToCgbBGPals::
; exists to forego reinserting cgb-converted image data
; input: a -> bgp
ldh [rBGP], a
push af
; Don't need to be here if DMG
ldh a, [hCGB]
and a
jr z, .end
push hl
push de
push bc
ldh a, [rSVBK]
push af
ld a, BANK(wBGPals2)
ldh [rSVBK], a
; copy & reorder bg pal buffer
ld hl, wBGPals2 ; to
ld de, wBGPals1 ; from
; order
ldh a, [rBGP]
ld b, a
; all pals
ld c, 8
call CopyPals
; request pal update
ld a, TRUE
ldh [hCGBPalUpdate], a
pop af
ldh [rSVBK], a
pop bc
pop de
pop hl
.end
pop af
ret
DmgToCgbObjPals::
; exists to forego reinserting cgb-converted image data
; input: d -> obp1
; e -> obp2
ld a, e
ldh [rOBP0], a
ld a, d
ldh [rOBP1], a
ldh a, [hCGB]
and a
ret z
push hl
push de
push bc
ldh a, [rSVBK]
push af
ld a, BANK(wOBPals2)
ldh [rSVBK], a
; copy & reorder obj pal buffer
ld hl, wOBPals2 ; to
ld de, wOBPals1 ; from
; order
ldh a, [rOBP0]
ld b, a
; all pals
ld c, 8
call CopyPals
; request pal update
ld a, TRUE
ldh [hCGBPalUpdate], a
pop af
ldh [rSVBK], a
pop bc
pop de
pop hl
ret
DmgToCgbObjPal0::
ldh [rOBP0], a
push af
; Don't need to be here if not CGB
ldh a, [hCGB]
and a
jr z, .dmg
push hl
push de
push bc
ldh a, [rSVBK]
push af
ld a, BANK(wOBPals2)
ldh [rSVBK], a
ld hl, wOBPals2 palette 0
ld de, wOBPals1 palette 0
ldh a, [rOBP0]
ld b, a
ld c, 1
call CopyPals
ld a, TRUE
ldh [hCGBPalUpdate], a
pop af
ldh [rSVBK], a
pop bc
pop de
pop hl
.dmg
pop af
ret
DmgToCgbObjPal1::
ldh [rOBP1], a
push af
ldh a, [hCGB]
and a
jr z, .dmg
push hl
push de
push bc
ldh a, [rSVBK]
push af
ld a, BANK(wOBPals2)
ldh [rSVBK], a
ld hl, wOBPals2 palette 1
ld de, wOBPals1 palette 1
ldh a, [rOBP1]
ld b, a
ld c, 1
call CopyPals
ld a, TRUE
ldh [hCGBPalUpdate], a
pop af
ldh [rSVBK], a
pop bc
pop de
pop hl
.dmg
pop af
ret
CopyPals::
; copy c palettes in order b from de to hl
push bc
ld c, NUM_PAL_COLORS
.loop
push de
push hl
; get pal color
ld a, b
maskbits 1 << PAL_COLOR_SIZE
; 2 bytes per color
add a
ld l, a
ld h, 0
add hl, de
ld e, [hl]
inc hl
ld d, [hl]
; dest
pop hl
; write color
ld [hl], e
inc hl
ld [hl], d
inc hl
; next pal color
rept PAL_COLOR_SIZE
srl b
endr
; source
pop de
; done pal?
dec c
jr nz, .loop
; de += 8 (next pal)
ld a, PALETTE_SIZE
add e
jr nc, .ok
inc d
.ok
ld e, a
; how many more pals?
pop bc
dec c
jr nz, CopyPals
ret
ClearVBank1::
ldh a, [hCGB]
and a
ret z
ld a, 1
ldh [rVBK], a
ld hl, VRAM_Begin
ld bc, VRAM_End - VRAM_Begin
xor a
call ByteFill
xor a
ldh [rVBK], a
ret
ReloadSpritesNoPalettes::
ldh a, [hCGB]
and a
ret z
ldh a, [rSVBK]
push af
ld a, BANK(wBGPals2)
ldh [rSVBK], a
ld hl, wBGPals2
ld bc, (8 palettes) + (2 palettes)
xor a
call ByteFill
pop af
ldh [rSVBK], a
ld a, TRUE
ldh [hCGBPalUpdate], a
jp DelayFrame
SwapTextboxPalettes::
homecall _SwapTextboxPalettes
ret
ScrollBGMapPalettes::
homecall _ScrollBGMapPalettes
ret

110
home/pokedex_flags.asm Normal file
View file

@ -0,0 +1,110 @@
CountSetBits::
; Count the number of set bits in b bytes starting from hl.
; Return in a, c and [wNumSetBits].
ld c, 0
.next
ld a, [hli]
ld e, a
ld d, 8
.count
srl e
ld a, 0
adc c
ld c, a
dec d
jr nz, .count
dec b
jr nz, .next
ld a, c
ld [wNumSetBits], a
ret
CountSetBits16::
; Count the number of set bits in bc bytes starting from hl - assumes bc < $1000
; Returns in bc; hl points to the end of the buffer; clobbers everything else
; Assumes that CountSetBits will leave hl pointing to the end of the buffer
swap b
ld a, c
swap a
and $f
or b
ld d, a
ld a, c
jr z, .small_count
ld bc, 0
and $f
jr z, .loop
push de
call .small_count
pop de
.loop
push bc
push de
ld b, $10
call CountSetBits
pop de
pop bc
add a, c
ld c, a
jr nc, .handle_loop
inc b
.handle_loop
dec d
jr nz, .loop
ret
.small_count
ld b, a
call CountSetBits
ld b, 0
ret
GetWeekday::
ld a, [wCurDay]
.mod
sub 7
jr nc, .mod
add 7
ret
SetSeenAndCaughtMon::
call GetPokemonFlagIndex
push de
call SetSeenMonIndex
pop de
SetCaughtMonIndex::
ld hl, wPokedexCaught
jr SetPokedexStatusMonIndex
SetSeenMon::
call GetPokemonFlagIndex
SetSeenMonIndex::
ld hl, wPokedexSeen
SetPokedexStatusMonIndex:
ld b, SET_FLAG
jr FlagActionBaseOne
CheckCaughtMon::
call GetPokemonFlagIndex
CheckCaughtMonIndex::
ld hl, wPokedexCaught
jr CheckPokedexStatusMonIndex
CheckSeenMon::
call GetPokemonFlagIndex
CheckSeenMonIndex::
ld hl, wPokedexSeen
CheckPokedexStatusMonIndex:
ld b, CHECK_FLAG
FlagActionBaseOne:
dec de
jp FlagAction
GetPokemonFlagIndex:
call GetPokemonIndexFromID
ld d, h
ld e, l
ret

318
home/pokemon.asm Normal file
View file

@ -0,0 +1,318 @@
IsAPokemon::
; Return carry if species a is not a Pokemon.
and a
jr z, .NotAPokemon
cp EGG
jr z, .Pokemon
cp MON_TABLE_ENTRIES + 1
jr c, .Pokemon
.NotAPokemon:
scf
ret
.Pokemon:
and a
ret
DrawBattleHPBar::
; Draw an HP bar d tiles long at hl
; Fill it up to e pixels
push hl
push de
push bc
; Place 'HP:'
ld a, $60
ld [hli], a
ld a, $61
ld [hli], a
; Draw a template
push hl
ld a, $62 ; empty bar
.template
ld [hli], a
dec d
jr nz, .template
ld a, $6b ; bar end
add b
ld [hl], a
pop hl
; Safety check # pixels
ld a, e
and a
jr nz, .fill
ld a, c
and a
jr z, .done
ld e, 1
.fill
; Keep drawing tiles until pixel length is reached
ld a, e
sub TILE_WIDTH
jr c, .lastbar
ld e, a
ld a, $6a ; full bar
ld [hli], a
ld a, e
and a
jr z, .done
jr .fill
.lastbar
ld a, $62 ; empty bar
add e ; + e
ld [hl], a
.done
pop bc
pop de
pop hl
ret
PrepMonFrontpic::
ld a, $1
ld [wBoxAlignment], a
_PrepMonFrontpic::
ld a, [wCurPartySpecies]
call IsAPokemon
jr c, .not_pokemon
push hl
ld de, vTiles2
predef GetMonFrontpic
pop hl
xor a
ldh [hGraphicStartTile], a
lb bc, 7, 7
predef PlaceGraphic
xor a
ld [wBoxAlignment], a
ret
.not_pokemon
xor a
ld [wBoxAlignment], a
inc a
ld [wCurPartySpecies], a
ret
PlayStereoCry::
push af
ld a, 1
ld [wStereoPanningMask], a
pop af
call _PlayMonCry
call WaitSFX
ret
PlayStereoCry2::
; Don't wait for the cry to end.
; Used during pic animations.
push af
ld a, 1
ld [wStereoPanningMask], a
pop af
jp _PlayMonCry
PlayMonCry::
call PlayMonCry2
call WaitSFX
ret
PlayMonCry2::
; Don't wait for the cry to end.
push af
xor a
ld [wStereoPanningMask], a
ld [wCryTracks], a
pop af
call _PlayMonCry
ret
_PlayMonCry::
push hl
push de
push bc
call GetCryIndex
jr c, .done
ld e, c
ld d, b
call PlayCry
.done
pop bc
pop de
pop hl
ret
LoadCry::
call GetCryIndex
ret c
ldh a, [hROMBank]
push af
ld a, BANK(PokemonCries)
rst Bankswitch
ld hl, PokemonCries
rept MON_CRY_LENGTH
add hl, bc
endr
ld e, [hl]
inc hl
ld d, [hl]
inc hl
ld a, [hli]
ld [wCryPitch], a
ld a, [hli]
ld [wCryPitch + 1], a
ld a, [hli]
ld [wCryLength], a
ld a, [hl]
ld [wCryLength + 1], a
pop af
rst Bankswitch
and a
ret
GetCryIndex::
and a
jr z, .no
cp MON_TABLE_ENTRIES + 1
jr nc, .no
push hl
call GetPokemonIndexFromID
dec hl
ld b, h
ld c, l
pop hl
and a
ret
.no
scf
ret
PrintLevel::
; Print wTempMonLevel at hl
ld a, [wTempMonLevel]
ld [hl], "<LV>"
inc hl
; How many digits?
ld c, 2
cp 100 ; This is distinct from MAX_LEVEL.
jr c, Print8BitNumLeftAlign
; 3-digit numbers overwrite the :L.
dec hl
inc c
jr Print8BitNumLeftAlign
PrintLevel_Force3Digits::
; Print :L and all 3 digits
ld [hl], "<LV>"
inc hl
ld c, 3
Print8BitNumLeftAlign::
ld [wTextDecimalByte], a
ld de, wTextDecimalByte
ld b, PRINTNUM_LEFTALIGN | 1
jp PrintNum
GetBaseData::
push bc
push de
push hl
ldh a, [hROMBank]
push af
; Egg doesn't have BaseData
ld a, [wCurSpecies]
cp EGG
jr z, .egg
; Get BaseData
call GetPokemonIndexFromID
ld b, h
ld c, l
ld a, BANK(BaseData)
ld hl, BaseData
call LoadIndirectPointer
; jr z, <some error handler>
rst Bankswitch
ld de, wCurBaseData
ld bc, BASE_DATA_SIZE
call CopyBytes
jr .end
.egg
ld de, UnusedEggPic
; Sprite dimensions
ld b, $55 ; 5x5
ld hl, wBasePicSize
ld [hl], b
; Beta front and back sprites
; (see pokegold-spaceworld's data/pokemon/base_stats/*)
ld hl, wBaseUnusedFrontpic
ld [hl], e
inc hl
ld [hl], d
inc hl
ld [hl], e
inc hl
ld [hl], d
jr .end ; useless
.end
; Replace Pokedex # with species
ld a, [wCurSpecies]
ld [wBaseSpecies], a
pop af
rst Bankswitch
pop hl
pop de
pop bc
ret
GetCurNickname::
ld a, [wCurPartyMon]
ld hl, wPartyMonNicknames
GetNickname::
; Get nickname a from list hl.
push hl
push bc
call SkipNames
ld de, wStringBuffer1
push de
ld bc, MON_NAME_LENGTH
call CopyBytes
pop de
callfar CorrectNickErrors
pop bc
pop hl
ret

52
home/predef.asm Normal file
View file

@ -0,0 +1,52 @@
Predef::
; Call predefined function a.
; Preserves bc, de, hl and f.
ld [wPredefID], a
ldh a, [hROMBank]
push af
ld a, BANK(GetPredefPointer)
rst Bankswitch
call GetPredefPointer ; stores hl in wPredefHL
; Switch to the new function's bank
rst Bankswitch
; Instead of directly calling stuff,
; push it to the stack in reverse.
ld hl, .Return
push hl
; Call the Predef function
ld a, [wPredefAddress]
ld h, a
ld a, [wPredefAddress + 1]
ld l, a
push hl
; Get hl back
ld a, [wPredefHL]
ld h, a
ld a, [wPredefHL + 1]
ld l, a
ret
.Return:
; Clean up after the Predef call
ld a, h
ld [wPredefHL], a
ld a, l
ld [wPredefHL + 1], a
pop hl
ld a, h
rst Bankswitch
ld a, [wPredefHL]
ld h, a
ld a, [wPredefHL + 1]
ld l, a
ret

79
home/print_bcd.asm Normal file
View file

@ -0,0 +1,79 @@
PrintBCDNumber::
; 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.
ld b, c ; save flags in b
res PRINTNUM_LEADINGZEROS_F, c
res PRINTNUM_LEFTALIGN_F, c
res PRINTNUM_MONEY_F, c ; c now holds the length
bit PRINTNUM_MONEY_F, b
jr z, .loop
bit PRINTNUM_LEADINGZEROS_F, b
jr nz, .loop ; skip currency symbol
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 PRINTNUM_LEADINGZEROS_F, b
ret z ; if so, we are done
; every digit of the BCD number is zero
bit PRINTNUM_LEFTALIGN_F, b
jr nz, .skipLeftAlignmentAdjustment
; the string is left-aligned; it needs to be moved back one space
dec hl
.skipLeftAlignmentAdjustment
bit PRINTNUM_MONEY_F, b
jr z, .skipCurrencySymbol
ld [hl], "¥" ; currency symbol
inc hl
.skipCurrencySymbol
ld [hl], "0"
call PrintLetterDelay
inc hl
ret
PrintBCDDigit::
and %00001111
and a
jr z, .zeroDigit
; nonzero digit
bit PRINTNUM_LEADINGZEROS_F, b ; have any non-space characters been printed?
jr z, .outputDigit
; if bit 7 is set, then no numbers have been printed yet
bit PRINTNUM_MONEY_F, b
jr z, .skipCurrencySymbol
ld [hl], "¥"
inc hl
res PRINTNUM_MONEY_F, b
.skipCurrencySymbol
res PRINTNUM_LEADINGZEROS_F, b ; unset 7 to indicate that a nonzero digit has been reached
.outputDigit
add "0"
ld [hli], a
jp PrintLetterDelay
.zeroDigit
bit PRINTNUM_LEADINGZEROS_F, b ; either printing leading zeroes or already reached a nonzero digit?
jr z, .outputDigit ; if so, print a zero digit
bit PRINTNUM_LEFTALIGN_F, b
ret nz
ld a, " "
ld [hli], a ; if right-aligned, "print" a space by advancing the pointer
ret

127
home/print_text.asm Normal file
View file

@ -0,0 +1,127 @@
PrintLetterDelay::
; Wait before printing the next letter.
; The text speed setting in wOptions is actually a frame count:
; fast: 1 frame
; mid: 3 frames
; slow: 5 frames
; wTextboxFlags[!0] and A or B override text speed with a one-frame delay.
; wOptions[4] and wTextboxFlags[!1] disable the delay.
ld a, [wOptions]
bit NO_TEXT_SCROLL, a
ret nz
; non-scrolling text?
ld a, [wTextboxFlags]
bit NO_TEXT_DELAY_F, a
ret z
push hl
push de
push bc
; force fast scroll?
ld a, [wTextboxFlags]
bit FAST_TEXT_DELAY_F, a
jr z, .fast
; text speed
ld a, [wOptions]
and %111
jr .updatedelay
.fast
ld a, TEXT_DELAY_FAST
.updatedelay
ld [wTextDelayFrames], a
.checkjoypad
call GetJoypad
; input override
ld a, [wDisableTextAcceleration]
and a
jr nz, .wait
; Wait one frame if holding A or B.
ldh a, [hJoyDown]
bit A_BUTTON_F, a
jr z, .checkb
jr .delay
.checkb
bit B_BUTTON_F, a
jr z, .wait
.delay
call DelayFrame
jr .end
.wait
ld a, [wTextDelayFrames]
and a
jr nz, .checkjoypad
.end
pop bc
pop de
pop hl
ret
CopyDataUntil::
; Copy [hl .. bc) to de.
; In other words, the source data is
; from hl up to but not including bc,
; and the destination is de.
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
PrintNum::
homecall _PrintNum
ret
MobilePrintNum::
homecall _MobilePrintNum
ret
FarPrintText::
ldh [hTempBank], a
ldh a, [hROMBank]
push af
ldh a, [hTempBank]
rst Bankswitch
call PrintText
pop af
rst Bankswitch
ret
CallPointerAt::
ldh a, [hROMBank]
push af
ld a, [hli]
rst Bankswitch
ld a, [hli]
ld h, [hl]
ld l, a
call _hl_
pop hl
ld a, h
rst Bankswitch
ret

41
home/printer.asm Normal file
View file

@ -0,0 +1,41 @@
PrinterReceive::
homecall _PrinterReceive
ret
AskSerial::
; send out a handshake while serial int is off
ld a, [wPrinterConnectionOpen]
bit 0, a
ret z
; if we're still interpreting data, don't try to receive
ld a, [wPrinterOpcode]
and a
ret nz
; once every 6 frames
ld hl, wHandshakeFrameDelay
inc [hl]
ld a, [hl]
cp 6
ret c
xor a
ld [hl], a
ld a, $0c
ld [wPrinterOpcode], a
; handshake
ld a, $88
ldh [rSB], a
; switch to internal clock
ld a, (0 << rSC_ON) | (1 << rSC_CLOCK)
ldh [rSC], a
; start transfer
ld a, (1 << rSC_ON) | (1 << rSC_CLOCK)
ldh [rSC], a
ret

12
home/queue_script.asm Normal file
View file

@ -0,0 +1,12 @@
QueueScript::
; Push pointer hl in the current bank to wQueuedScriptBank.
ldh a, [hROMBank]
FarQueueScript::
; Push pointer a:hl to wQueuedScriptBank.
ld [wQueuedScriptBank], a
ld a, l
ld [wQueuedScriptAddr], a
ld a, h
ld [wQueuedScriptAddr + 1], a
ret

80
home/random.asm Normal file
View file

@ -0,0 +1,80 @@
Random::
; A simple hardware-based random number generator (RNG).
; Two random numbers are generated by adding and subtracting
; the divider to the respective values every time it's called.
; The divider is a register that increments at a rate of 16384Hz.
; For comparison, the Game Boy operates at a clock speed of 4.2MHz.
; Additionally, an equivalent function is executed in VBlank.
; This leaves a with the value in hRandomSub.
push bc
ldh a, [rDIV]
ld b, a
ldh a, [hRandomAdd]
adc b
ldh [hRandomAdd], a
ldh a, [rDIV]
ld b, a
ldh a, [hRandomSub]
sbc b
ldh [hRandomSub], a
pop bc
ret
BattleRandom::
; _BattleRandom lives in another bank.
; It handles all RNG calls in the battle engine, allowing
; link battles to remain in sync using a shared PRNG.
ldh a, [hROMBank]
push af
ld a, BANK(_BattleRandom)
rst Bankswitch
call _BattleRandom
ld [wPredefHL + 1], a
pop af
rst Bankswitch
ld a, [wPredefHL + 1]
ret
RandomRange::
; Return a random number between 0 and a (non-inclusive).
push bc
ld c, a
; b = $100 % c
xor a
sub c
.mod
sub c
jr nc, .mod
add c
ld b, a
; Get a random number
; from 0 to $ff - b.
push bc
.loop
call Random
ldh a, [hRandomAdd]
ld c, a
add b
jr c, .loop
ld a, c
pop bc
call SimpleDivide
pop bc
ret

32
home/region.asm Normal file
View file

@ -0,0 +1,32 @@
IsInJohto::
; Return 0 if the player is in Johto, and 1 in Kanto.
ld a, [wMapGroup]
ld b, a
ld a, [wMapNumber]
ld c, a
call GetWorldMapLocation
cp LANDMARK_FAST_SHIP
jr z, .Johto
cp LANDMARK_SPECIAL
jr nz, .CheckRegion
ld a, [wBackupMapGroup]
ld b, a
ld a, [wBackupMapNumber]
ld c, a
call GetWorldMapLocation
.CheckRegion:
cp KANTO_LANDMARK
jr nc, .Kanto
.Johto:
xor a ; JOHTO_REGION
ret
.Kanto:
ld a, KANTO_REGION
ret

61
home/scrolling_menu.asm Normal file
View file

@ -0,0 +1,61 @@
ScrollingMenu::
call CopyMenuData
ldh a, [hROMBank]
push af
ld a, BANK(_ScrollingMenu) ; aka BANK(_InitScrollingMenu)
rst Bankswitch
call _InitScrollingMenu
call .UpdatePalettes
call _ScrollingMenu
pop af
rst Bankswitch
ld a, [wMenuJoypad]
ret
.UpdatePalettes:
ld hl, wVramState
bit 0, [hl]
jp nz, UpdateTimePals
jp SetPalettes
InitScrollingMenu::
ld a, [wMenuBorderTopCoord]
dec a
ld b, a
ld a, [wMenuBorderBottomCoord]
sub b
ld d, a
ld a, [wMenuBorderLeftCoord]
dec a
ld c, a
ld a, [wMenuBorderRightCoord]
sub c
ld e, a
push de
call Coord2Tile
pop bc
jp Textbox
JoyTextDelay_ForcehJoyDown::
call DelayFrame
ldh a, [hInMenu]
push af
ld a, $1
ldh [hInMenu], a
call JoyTextDelay
pop af
ldh [hInMenu], a
ldh a, [hJoyLast]
and D_RIGHT + D_LEFT + D_UP + D_DOWN
ld c, a
ldh a, [hJoyPressed]
and A_BUTTON + B_BUTTON + SELECT + START
or c
ld c, a
ret

407
home/serial.asm Normal file
View file

@ -0,0 +1,407 @@
Serial::
; The serial interrupt.
push af
push bc
push de
push hl
ldh a, [hMobileReceive]
and a
jr nz, .mobile
ld a, [wPrinterConnectionOpen]
bit 0, a
jr nz, .printer
ldh a, [hSerialConnectionStatus]
inc a ; is it equal to CONNECTION_NOT_ESTABLISHED?
jr z, .establish_connection
ldh a, [rSB]
ldh [hSerialReceive], a
ldh a, [hSerialSend]
ldh [rSB], a
ldh a, [hSerialConnectionStatus]
cp USING_INTERNAL_CLOCK
jr z, .player2
ld a, (0 << rSC_ON) | (0 << rSC_CLOCK)
ldh [rSC], a
ld a, (1 << rSC_ON) | (0 << rSC_CLOCK)
ldh [rSC], a
jr .player2
.mobile
call MobileReceive
jr .end
.printer
call PrinterReceive
jr .end
.establish_connection
ldh a, [rSB]
cp USING_EXTERNAL_CLOCK
jr z, .player1
cp USING_INTERNAL_CLOCK
jr nz, .player2
.player1
ldh [hSerialReceive], a
ldh [hSerialConnectionStatus], a
cp USING_INTERNAL_CLOCK
jr z, ._player2
xor a
ldh [rSB], a
ld a, 3
ldh [rDIV], a
.delay_loop
ldh a, [rDIV]
bit 7, a
jr nz, .delay_loop
ld a, (0 << rSC_ON) | (0 << rSC_CLOCK)
ldh [rSC], a
ld a, (1 << rSC_ON) | (0 << rSC_CLOCK)
ldh [rSC], a
jr .player2
._player2
xor a
ldh [rSB], a
.player2
ld a, TRUE
ldh [hSerialReceivedNewData], a
ld a, SERIAL_NO_DATA_BYTE
ldh [hSerialSend], a
.end
pop hl
pop de
pop bc
pop af
reti
Serial_ExchangeBytes::
; send bc bytes from hl, receive bc bytes to de
ld a, TRUE
ldh [hSerialIgnoringInitialData], a
.loop
ld a, [hl]
ldh [hSerialSend], a
call Serial_ExchangeByte
push bc
ld b, a
inc hl
ld a, 48
.wait
dec a
jr nz, .wait
ldh a, [hSerialIgnoringInitialData]
and a
ld a, b
pop bc
jr z, .load
dec hl
cp SERIAL_PREAMBLE_BYTE
jr nz, .loop
xor a ; FALSE
ldh [hSerialIgnoringInitialData], a
jr .loop
.load
ld [de], a
inc de
dec bc
ld a, b
or c
jr nz, .loop
ret
Serial_ExchangeByte::
.timeout_loop
xor a
ldh [hSerialReceivedNewData], a
ldh a, [hSerialConnectionStatus]
cp USING_INTERNAL_CLOCK
jr nz, .not_player_2
ld a, (0 << rSC_ON) | (1 << rSC_CLOCK)
ldh [rSC], a
ld a, (1 << rSC_ON) | (1 << rSC_CLOCK)
ldh [rSC], a
.not_player_2
.loop
ldh a, [hSerialReceivedNewData]
and a
jr nz, .await_new_data
ldh a, [hSerialConnectionStatus]
cp USING_EXTERNAL_CLOCK
jr nz, .not_player_1_or_timed_out
call CheckLinkTimeoutFramesNonzero
jr z, .not_player_1_or_timed_out
call .ShortDelay
push hl
ld hl, wLinkTimeoutFrames + 1
inc [hl]
jr nz, .no_rollover_up
dec hl
inc [hl]
.no_rollover_up
pop hl
call CheckLinkTimeoutFramesNonzero
jr nz, .loop
jp SerialDisconnected
.not_player_1_or_timed_out
ldh a, [rIE]
and (1 << SERIAL) | (1 << TIMER) | (1 << LCD_STAT) | (1 << VBLANK)
cp 1 << SERIAL
jr nz, .loop
ld a, [wLinkByteTimeout]
dec a
ld [wLinkByteTimeout], a
jr nz, .loop
ld a, [wLinkByteTimeout + 1]
dec a
ld [wLinkByteTimeout + 1], a
jr nz, .loop
ldh a, [hSerialConnectionStatus]
cp USING_EXTERNAL_CLOCK
jr z, .await_new_data
ld a, 255
.long_delay_loop
dec a
jr nz, .long_delay_loop
.await_new_data
xor a
ldh [hSerialReceivedNewData], a
ldh a, [rIE]
and (1 << SERIAL) | (1 << TIMER) | (1 << LCD_STAT) | (1 << VBLANK)
sub 1 << SERIAL
jr nz, .non_serial_interrupts_enabled
; a == 0
assert LOW(SERIAL_LINK_BYTE_TIMEOUT) == 0
ld [wLinkByteTimeout], a
ld a, HIGH(SERIAL_LINK_BYTE_TIMEOUT)
ld [wLinkByteTimeout + 1], a
.non_serial_interrupts_enabled
ldh a, [hSerialReceive]
cp SERIAL_NO_DATA_BYTE
ret nz
call CheckLinkTimeoutFramesNonzero
jr z, .timed_out
push hl
ld hl, wLinkTimeoutFrames + 1
ld a, [hl]
dec a
ld [hld], a
inc a
jr nz, .no_rollover
dec [hl]
.no_rollover
pop hl
call CheckLinkTimeoutFramesNonzero
jr z, SerialDisconnected
.timed_out
ldh a, [rIE]
and (1 << SERIAL) | (1 << TIMER) | (1 << LCD_STAT) | (1 << VBLANK)
cp 1 << SERIAL
ld a, SERIAL_NO_DATA_BYTE
ret z
ld a, [hl]
ldh [hSerialSend], a
call DelayFrame
jp .timeout_loop
.ShortDelay:
ld a, 15
.short_delay_loop
dec a
jr nz, .short_delay_loop
ret
CheckLinkTimeoutFramesNonzero::
push hl
ld hl, wLinkTimeoutFrames
ld a, [hli]
or [hl]
pop hl
ret
; This sets wLinkTimeoutFrames to $ffff, since
; a is always 0 when it is called.
SerialDisconnected::
dec a
ld [wLinkTimeoutFrames], a
ld [wLinkTimeoutFrames + 1], a
ret
; This is used to check that both players entered the same Cable Club room.
Serial_ExchangeSyncBytes::
ld hl, wLinkPlayerSyncBuffer
ld de, wLinkReceivedSyncBuffer
ld c, 2
ld a, TRUE
ldh [hSerialIgnoringInitialData], a
.exchange
call DelayFrame
ld a, [hl]
ldh [hSerialSend], a
call Serial_ExchangeByte
ld b, a
inc hl
ldh a, [hSerialIgnoringInitialData]
and a
ld a, FALSE
ldh [hSerialIgnoringInitialData], a
jr nz, .exchange
ld a, b
ld [de], a
inc de
dec c
jr nz, .exchange
ret
Serial_PrintWaitingTextAndSyncAndExchangeNybble::
call LoadTilemapToTempTilemap
callfar PlaceWaitingText
call WaitLinkTransfer
jp SafeLoadTempTilemapToTilemap
Serial_SyncAndExchangeNybble:: ; unreferenced
call LoadTilemapToTempTilemap
callfar PlaceWaitingText
jp WaitLinkTransfer ; pointless
WaitLinkTransfer::
vc_hook Wireless_WaitLinkTransfer
ld a, $ff
ld [wOtherPlayerLinkAction], a
.loop
call LinkTransfer
call DelayFrame
call CheckLinkTimeoutFramesNonzero
jr z, .check
push hl
ld hl, wLinkTimeoutFrames + 1
dec [hl]
jr nz, .skip
dec hl
dec [hl]
jr nz, .skip
; We might be disconnected
pop hl
xor a
jp SerialDisconnected
.skip
pop hl
.check
ld a, [wOtherPlayerLinkAction]
inc a
jr z, .loop
vc_patch Wireless_net_delay_1
if DEF(_CRYSTAL_VC)
ld b, 26
else
ld b, 10
endc
vc_patch_end
.receive
call DelayFrame
call LinkTransfer
dec b
jr nz, .receive
vc_patch Wireless_net_delay_2
if DEF(_CRYSTAL_VC)
ld b, 26
else
ld b, 10
endc
vc_patch_end
.acknowledge
call DelayFrame
call LinkDataReceived
dec b
jr nz, .acknowledge
ld a, [wOtherPlayerLinkAction]
ld [wOtherPlayerLinkMode], a
vc_hook Wireless_WaitLinkTransfer_ret
ret
LinkTransfer::
push bc
ld b, SERIAL_TIMECAPSULE
ld a, [wLinkMode]
cp LINK_TIMECAPSULE
jr z, .got_high_nybble
ld b, SERIAL_TIMECAPSULE
jr c, .got_high_nybble
cp LINK_TRADECENTER
ld b, SERIAL_TRADECENTER
jr z, .got_high_nybble
ld b, SERIAL_BATTLE
.got_high_nybble
call .Receive
ld a, [wPlayerLinkAction]
add b
ldh [hSerialSend], a
ldh a, [hSerialConnectionStatus]
cp USING_INTERNAL_CLOCK
jr nz, .player_1
ld a, (0 << rSC_ON) | (1 << rSC_CLOCK)
ldh [rSC], a
ld a, (1 << rSC_ON) | (1 << rSC_CLOCK)
ldh [rSC], a
.player_1
call .Receive
pop bc
ret
.Receive:
ldh a, [hSerialReceive]
ld [wOtherPlayerLinkMode], a
and $f0
cp b
ret nz
xor a
ldh [hSerialReceive], a
ld a, [wOtherPlayerLinkMode]
and $f
ld [wOtherPlayerLinkAction], a
ret
LinkDataReceived::
; Let the other system know that the data has been received.
xor a
ldh [hSerialSend], a
ldh a, [hSerialConnectionStatus]
cp USING_INTERNAL_CLOCK
ret nz
ld a, (0 << rSC_ON) | (1 << rSC_CLOCK)
ldh [rSC], a
ld a, (1 << rSC_ON) | (1 << rSC_CLOCK)
ldh [rSC], a
ret

9
home/sine.asm Normal file
View file

@ -0,0 +1,9 @@
Cosine:: ; unreferenced
; a = d * cos(a * pi/32)
add %010000 ; cos(x) = sin(x + pi/2)
; fallthrough
Sine::
; a = d * sin(a * pi/32)
ld e, a
homecall _Sine
ret

31
home/sprite_anims.asm Normal file
View file

@ -0,0 +1,31 @@
InitSpriteAnimStruct::
ld [wSpriteAnimID], a
ldh a, [hROMBank]
push af
ld a, BANK(_InitSpriteAnimStruct)
rst Bankswitch
ld a, [wSpriteAnimID]
call _InitSpriteAnimStruct
pop af
rst Bankswitch
ret
ReinitSpriteAnimFrame::
ld [wSpriteAnimID], a
ldh a, [hROMBank]
push af
ld a, BANK(_ReinitSpriteAnimFrame)
rst Bankswitch
ld a, [wSpriteAnimID]
call _ReinitSpriteAnimFrame
pop af
rst Bankswitch
ret

19
home/sprite_updates.asm Normal file
View file

@ -0,0 +1,19 @@
DisableSpriteUpdates::
xor a
ldh [hMapAnims], a
ld a, [wVramState]
res 0, a
ld [wVramState], a
xor a
ld [wSpriteUpdatesEnabled], a
ret
EnableSpriteUpdates::
ld a, $1
ld [wSpriteUpdatesEnabled], a
ld a, [wVramState]
set 0, a
ld [wVramState], a
ld a, $1
ldh [hMapAnims], a
ret

30
home/sram.asm Normal file
View file

@ -0,0 +1,30 @@
OpenSRAM::
; if invalid bank, sram is disabled
cp NUM_SRAM_BANKS
jr nc, CloseSRAM
; switch to sram bank a
push af
; latch clock data
ld a, 1
ld [MBC3LatchClock], a
; enable sram/clock write
ld a, SRAM_ENABLE
ld [MBC3SRamEnable], a
; select sram bank
pop af
ldh [hSRAMBank], a
ld [MBC3SRamBank], a
ret
CloseSRAM::
push af
ld a, -1
ldh [hSRAMBank], a
ld a, SRAM_DISABLE
; reset clock latch for next time
ld [MBC3LatchClock], a
; disable sram/clock write
ld [MBC3SRamEnable], a
pop af
ret

124
home/stone_queue.asm Normal file
View file

@ -0,0 +1,124 @@
HandleStoneQueue::
ldh a, [hROMBank]
push af
call SwitchToMapScriptsBank
call .WarpAction
pop bc
ld a, b
rst Bankswitch
ret
.WarpAction:
ld hl, OBJECT_MAP_OBJECT_INDEX
add hl, de
ld a, [hl]
cp $ff
ret nc
ld l, a
push hl
call .IsObjectOnWarp
pop hl
ret nc
ld d, a
ld e, l
call .IsObjectInStoneTable
ret nc
call CallMapScript
farcall EnableScriptMode
scf
ret
.IsObjectOnWarp:
push de
ld hl, OBJECT_MAP_X
add hl, de
ld a, [hl]
ld hl, OBJECT_MAP_Y
add hl, de
ld e, [hl]
sub 4
ld d, a
ld a, e
sub 4
ld e, a
call .check_on_warp
pop de
ret
.check_on_warp
ld hl, wCurMapWarpsPointer
ld a, [hli]
ld h, [hl]
ld l, a
ld a, [wCurMapWarpCount]
and a
ret z
.loop
push af
ld a, [hl]
cp e
jr nz, .not_on_warp
inc hl
ld a, [hld]
cp d
jr z, .found_warp
.not_on_warp
ld a, WARP_EVENT_SIZE
add l
ld l, a
jr nc, .no_carry
inc h
.no_carry
pop af
dec a
jr nz, .loop
and a
ret
.found_warp
pop af
ld d, a
ld a, [wCurMapWarpCount]
sub d
inc a
scf
ret
.IsObjectInStoneTable:
inc e
ld hl, CMDQUEUE_ADDR
add hl, bc
ld a, [hli]
ld h, [hl]
ld l, a
.loop2
ld a, [hli]
cp $ff
ret z
cp d
jr nz, .next_inc3
ld a, [hli]
cp e
jr nz, .next_inc2
ld a, [hli]
ld h, [hl]
ld l, a
scf
ret
.next_inc3
inc hl
.next_inc2
inc hl
inc hl
jr .loop2

35
home/string.asm Normal file
View file

@ -0,0 +1,35 @@
InitString::
; Init a string of length c.
push hl
jr _InitString
InitName::
; Intended for names, so this function is limited to ten characters.
push hl
ld c, NAME_LENGTH - 1
_InitString::
; if the string pointed to by hl is empty (defined as "zero or more spaces
; followed by a null"), then initialize it to the string pointed to by de.
push bc
.loop
ld a, [hli]
cp "@"
jr z, .blank
cp " "
jr nz, .notblank
dec c
jr nz, .loop
.blank
pop bc
ld l, e
ld h, d
pop de
ld b, 0
inc c
call CopyBytes
ret
.notblank
pop bc
pop hl
ret

1044
home/text.asm Normal file

File diff suppressed because it is too large Load diff

226
home/tilemap.asm Normal file
View file

@ -0,0 +1,226 @@
ClearBGPalettes::
call ClearPalettes
WaitBGMap::
; Tell VBlank to update BG Map
ld a, 1 ; BG Map 0 tiles
ldh [hBGMapMode], a
; Wait for it to do its magic
ld c, 4
jp DelayFrames
WaitBGMap2::
ldh a, [hCGB]
and a
jr z, .bg0
ld a, 2
ldh [hBGMapMode], a
ld c, 4
call DelayFrames
.bg0
ld a, 1
ldh [hBGMapMode], a
ld c, 4
jp DelayFrames
IsCGB::
ldh a, [hCGB]
and a
ret
ApplyTilemap::
ldh a, [hCGB]
and a
jr z, .dmg
ld a, [wSpriteUpdatesEnabled]
and a
jr z, .dmg
ld a, 1
ldh [hBGMapMode], a
jr CopyTilemapAtOnce
.dmg
; WaitBGMap
ld a, 1
ldh [hBGMapMode], a
ld c, 4
jp DelayFrames
CGBOnly_CopyTilemapAtOnce::
ldh a, [hCGB]
and a
jr z, WaitBGMap
CopyTilemapAtOnce::
ldh a, [hBGMapMode]
push af
xor a
ldh [hBGMapMode], a
ldh a, [hMapAnims]
push af
xor a
ldh [hMapAnims], a
.wait
ldh a, [rLY]
cp $80 - 1
jr c, .wait
di
ld a, BANK(vBGMap2)
ldh [rVBK], a
hlcoord 0, 0, wAttrmap
call .CopyBGMapViaStack
ld a, BANK(vBGMap0)
ldh [rVBK], a
hlcoord 0, 0
call .CopyBGMapViaStack
.wait2
ldh a, [rLY]
cp $80 - 1
jr c, .wait2
ei
pop af
ldh [hMapAnims], a
pop af
ldh [hBGMapMode], a
ret
.CopyBGMapViaStack:
; Copy all tiles to vBGMap
ld [hSPBuffer], sp
ld sp, hl
ldh a, [hBGMapAddress + 1]
ld h, a
ld l, 0
ld a, SCREEN_HEIGHT
ldh [hTilesPerCycle], a
ld b, 1 << 1 ; not in v/hblank
ld c, LOW(rSTAT)
.loop
rept SCREEN_WIDTH / 2
pop de
; if in v/hblank, wait until not in v/hblank
.loop\@
ldh a, [c]
and b
jr nz, .loop\@
; load vBGMap
ld [hl], e
inc l
ld [hl], d
inc l
endr
ld de, BG_MAP_WIDTH - SCREEN_WIDTH
add hl, de
ldh a, [hTilesPerCycle]
dec a
ldh [hTilesPerCycle], a
jr nz, .loop
ldh a, [hSPBuffer]
ld l, a
ldh a, [hSPBuffer + 1]
ld h, a
ld sp, hl
ret
SetPalettes::
; Inits the Palettes
; depending on the system the monochromes palettes or color palettes
ldh a, [hCGB]
and a
jr nz, .SetPalettesForGameBoyColor
ld a, %11100100
ldh [rBGP], a
ld a, %11010000
ldh [rOBP0], a
ldh [rOBP1], a
ret
.SetPalettesForGameBoyColor:
push de
ld a, %11100100
call DmgToCgbBGPals
lb de, %11100100, %11100100
call DmgToCgbObjPals
pop de
ret
ClearPalettes::
; Make all palettes white
; CGB: make all the palette colors white
ldh a, [hCGB]
and a
jr nz, .cgb
; DMG: just change palettes to 0 (white)
xor a
ldh [rBGP], a
ldh [rOBP0], a
ldh [rOBP1], a
ret
.cgb
ldh a, [rSVBK]
push af
ld a, BANK(wBGPals2)
ldh [rSVBK], a
; Fill wBGPals2 and wOBPals2 with $ffff (white)
ld hl, wBGPals2
ld bc, 16 palettes
ld a, $ff
call ByteFill
pop af
ldh [rSVBK], a
; Request palette update
ld a, TRUE
ldh [hCGBPalUpdate], a
ret
GetMemSGBLayout::
ld b, SCGB_DEFAULT
GetSGBLayout::
; load sgb packets unless dmg
ldh a, [hCGB]
and a
jr nz, .sgb
ldh a, [hSGB]
and a
ret z
.sgb
predef_jump LoadSGBLayout
SetHPPal::
; Set palette for hp bar pixel length e at hl.
call GetHPPal
ld [hl], d
ret
GetHPPal::
; Get palette for hp bar pixel length e in d.
ld d, HP_GREEN
ld a, e
cp (HP_BAR_LENGTH_PX * 50 / 100) ; 24
ret nc
inc d ; HP_YELLOW
cp (HP_BAR_LENGTH_PX * 21 / 100) ; 10
ret nc
inc d ; HP_RED
ret

271
home/time.asm Normal file
View file

@ -0,0 +1,271 @@
; Functions relating to the timer interrupt and the real-time-clock.
Timer:: ; unreferenced
push af
ldh a, [hMobile]
and a
jr z, .not_mobile
call MobileTimer
.not_mobile
pop af
reti
LatchClock::
; latch clock counter data
ld a, 0
ld [MBC3LatchClock], a
ld a, 1
ld [MBC3LatchClock], a
ret
UpdateTime::
call GetClock
call FixDays
call FixTime
farcall GetTimeOfDay
ret
GetClock::
; store clock data in hRTCDayHi-hRTCSeconds
; enable clock r/w
ld a, SRAM_ENABLE
ld [MBC3SRamEnable], a
; clock data is 'backwards' in hram
call LatchClock
ld hl, MBC3SRamBank
ld de, MBC3RTC
ld [hl], RTC_S
ld a, [de]
maskbits 60
ldh [hRTCSeconds], a
ld [hl], RTC_M
ld a, [de]
maskbits 60
ldh [hRTCMinutes], a
ld [hl], RTC_H
ld a, [de]
maskbits 24
ldh [hRTCHours], a
ld [hl], RTC_DL
ld a, [de]
ldh [hRTCDayLo], a
ld [hl], RTC_DH
ld a, [de]
ldh [hRTCDayHi], a
; unlatch clock / disable clock r/w
jp CloseSRAM
FixDays::
; fix day count
; mod by 140
; check if day count > 255 (bit 8 set)
ldh a, [hRTCDayHi] ; DH
bit 0, a
jr z, .daylo
; reset dh (bit 8)
res 0, a
ldh [hRTCDayHi], a
; mod 140
; mod twice since bit 8 (DH) was set
ldh a, [hRTCDayLo]
.modh
sub 140
jr nc, .modh
.modl
sub 140
jr nc, .modl
add 140
; update dl
ldh [hRTCDayLo], a
; flag for sRTCStatusFlags
ld a, %01000000
jr .set
.daylo
; quit if fewer than 140 days have passed
ldh a, [hRTCDayLo]
cp 140
jr c, .quit
; mod 140
.mod
sub 140
jr nc, .mod
add 140
; update dl
ldh [hRTCDayLo], a
; flag for sRTCStatusFlags
ld a, %00100000
.set
; update clock with modded day value
push af
call SetClock
pop af
scf
ret
.quit
xor a
ret
FixTime::
; add ingame time (set at newgame) to current time
; store time in wCurDay, hHours, hMinutes, hSeconds
; second
ldh a, [hRTCSeconds]
ld c, a
ld a, [wStartSecond]
add c
sub 60
jr nc, .updatesec
add 60
.updatesec
ldh [hSeconds], a
; minute
ccf ; carry is set, so turn it off
ldh a, [hRTCMinutes]
ld c, a
ld a, [wStartMinute]
adc c
sub 60
jr nc, .updatemin
add 60
.updatemin
ldh [hMinutes], a
; hour
ccf ; carry is set, so turn it off
ldh a, [hRTCHours]
ld c, a
ld a, [wStartHour]
adc c
sub 24
jr nc, .updatehr
add 24
.updatehr
ldh [hHours], a
; day
ccf ; carry is set, so turn it off
ldh a, [hRTCDayLo]
ld c, a
ld a, [wStartDay]
adc c
ld [wCurDay], a
ret
InitTimeOfDay::
xor a
ld [wStringBuffer2], a
ld a, 0 ; useless
ld [wStringBuffer2 + 3], a
jr InitTime
InitDayOfWeek::
call UpdateTime
ldh a, [hHours]
ld [wStringBuffer2 + 1], a
ldh a, [hMinutes]
ld [wStringBuffer2 + 2], a
ldh a, [hSeconds]
ld [wStringBuffer2 + 3], a
jr InitTime ; useless
InitTime::
farcall _InitTime
ret
ClearClock::
xor a
ldh [hRTCSeconds], a
ldh [hRTCMinutes], a
ldh [hRTCHours], a
ldh [hRTCDayLo], a
ldh [hRTCDayHi], a
; fallthrough
SetClock::
; set clock data from hram
; enable clock r/w
ld a, SRAM_ENABLE
ld [MBC3SRamEnable], a
; set clock data
; stored 'backwards' in hram
call LatchClock
ld hl, MBC3SRamBank
ld de, MBC3RTC
; seconds
ld [hl], RTC_S
ldh a, [hRTCSeconds]
ld [de], a
; minutes
ld [hl], RTC_M
ldh a, [hRTCMinutes]
ld [de], a
; hours
ld [hl], RTC_H
ldh a, [hRTCHours]
ld [de], a
; day lo
ld [hl], RTC_DL
ldh a, [hRTCDayLo]
ld [de], a
; day hi
ld [hl], RTC_DH
ldh a, [hRTCDayHi]
res 6, a ; make sure timer is active
ld [de], a
; cleanup
jp CloseSRAM ; unlatch clock, disable clock r/w
ClearRTCStatus:: ; unreferenced
; clear sRTCStatusFlags
xor a
push af
ld a, BANK(sRTCStatusFlags)
call OpenSRAM
pop af
ld [sRTCStatusFlags], a
jp CloseSRAM
RecordRTCStatus::
; append flags to sRTCStatusFlags
ld hl, sRTCStatusFlags
push af
ld a, BANK(sRTCStatusFlags)
call OpenSRAM
pop af
or [hl]
ld [hl], a
jp CloseSRAM
CheckRTCStatus::
; check sRTCStatusFlags
ld a, BANK(sRTCStatusFlags)
call OpenSRAM
ld a, [sRTCStatusFlags]
jp CloseSRAM

22
home/time_palettes.asm Normal file
View file

@ -0,0 +1,22 @@
UpdateTimeAndPals::
; update time and time-sensitive palettes
; rtc enabled?
ld a, [wSpriteUpdatesEnabled]
cp 0
ret z
call UpdateTime
; obj update on?
ld a, [wVramState]
bit 0, a ; obj update
ret z
TimeOfDayPals::
callfar _TimeOfDayPals
ret
UpdateTimePals::
callfar _UpdateTimePals
ret

244
home/trainers.asm Normal file
View file

@ -0,0 +1,244 @@
CheckTrainerBattle::
ldh a, [hROMBank]
push af
call SwitchToMapScriptsBank
call _CheckTrainerBattle
pop bc
ld a, b
rst Bankswitch
ret
_CheckTrainerBattle::
; Check if any trainer on the map sees the player and wants to battle.
; Skip the player object.
ld a, 1
ld de, wMap1Object
.loop
; Start a battle if the object:
push af
push de
; Has a sprite
ld hl, MAPOBJECT_SPRITE
add hl, de
ld a, [hl]
and a
jr z, .next
; Is a trainer
ld hl, MAPOBJECT_TYPE
add hl, de
ld a, [hl]
and MAPOBJECT_TYPE_MASK
cp OBJECTTYPE_TRAINER
jr nz, .next
; Is visible on the map
ld hl, MAPOBJECT_OBJECT_STRUCT_ID
add hl, de
ld a, [hl]
cp -1
jr z, .next
; Is facing the player...
call GetObjectStruct
call FacingPlayerDistance_bc
jr nc, .next
; ...within their sight range
ld hl, MAPOBJECT_SIGHT_RANGE
add hl, de
ld a, [hl]
cp b
jr c, .next
; And hasn't already been beaten
push bc
push de
ld hl, MAPOBJECT_SCRIPT_POINTER
add hl, de
ld a, [hli]
ld h, [hl]
ld l, a
ld e, [hl]
inc hl
ld d, [hl]
ld b, CHECK_FLAG
call EventFlagAction
ld a, c
pop de
pop bc
and a
jr z, .startbattle
.next
pop de
ld hl, MAPOBJECT_LENGTH
add hl, de
ld d, h
ld e, l
pop af
inc a
cp NUM_OBJECTS
jr nz, .loop
xor a
ret
.startbattle
pop de
pop af
ldh [hLastTalked], a
ld a, b
ld [wSeenTrainerDistance], a
ld a, c
ld [wSeenTrainerDirection], a
jr LoadTrainer_continue
TalkToTrainer::
ld a, 1
ld [wSeenTrainerDistance], a
ld a, -1
ld [wSeenTrainerDirection], a
LoadTrainer_continue::
call GetMapScriptsBank
ld [wSeenTrainerBank], a
ldh a, [hLastTalked]
call GetMapObject
ld hl, MAPOBJECT_SCRIPT_POINTER
add hl, bc
ld a, [wSeenTrainerBank]
call GetFarWord
ld de, wTempTrainer
ld bc, wTempTrainerEnd - wTempTrainer
ld a, [wSeenTrainerBank]
call FarCopyBytes
xor a
ld [wRunningTrainerBattleScript], a
scf
ret
FacingPlayerDistance_bc::
push de
call FacingPlayerDistance
ld b, d
ld c, e
pop de
ret
FacingPlayerDistance::
; Return carry if the sprite at bc is facing the player,
; its distance in d, and its direction in e.
ld hl, OBJECT_MAP_X ; x
add hl, bc
ld d, [hl]
ld hl, OBJECT_MAP_Y ; y
add hl, bc
ld e, [hl]
ld a, [wPlayerMapX]
cp d
jr z, .CheckY
ld a, [wPlayerMapY]
cp e
jr z, .CheckX
and a
ret
.CheckY:
ld a, [wPlayerMapY]
sub e
jr z, .NotFacing
jr nc, .Above
; Below
cpl
inc a
ld d, a
ld e, OW_UP
jr .CheckFacing
.Above:
ld d, a
ld e, OW_DOWN
jr .CheckFacing
.CheckX:
ld a, [wPlayerMapX]
sub d
jr z, .NotFacing
jr nc, .Left
; Right
cpl
inc a
ld d, a
ld e, OW_LEFT
jr .CheckFacing
.Left:
ld d, a
ld e, OW_RIGHT
.CheckFacing:
call GetSpriteDirection
cp e
jr nz, .NotFacing
scf
ret
.NotFacing:
and a
ret
CheckTrainerFlag:: ; unreferenced
push bc
ld hl, OBJECT_MAP_OBJECT_INDEX
add hl, bc
ld a, [hl]
call GetMapObject
ld hl, MAPOBJECT_SCRIPT_POINTER
add hl, bc
ld a, [hli]
ld h, [hl]
ld l, a
call GetMapScriptsBank
call GetFarWord
ld d, h
ld e, l
push de
ld b, CHECK_FLAG
call EventFlagAction
pop de
ld a, c
and a
pop bc
ret
PrintWinLossText::
ld a, [wBattleResult]
ld hl, wWinTextPointer
and $f ; WIN?
jr z, .ok
ld hl, wLossTextPointer
.ok
ld a, [hli]
ld h, [hl]
ld l, a
call GetMapScriptsBank
call FarPrintText
call WaitBGMap
jp WaitPressAorB_BlinkCursor

421
home/vblank.asm Normal file
View file

@ -0,0 +1,421 @@
; VBlank is the interrupt responsible for updating VRAM.
; In Pokemon Crystal, VBlank has been hijacked to act as the
; main loop. After time-sensitive graphics operations have been
; performed, joypad input and sound functions are executed.
; This prevents the display and audio output from lagging.
VBlank::
push af
push bc
push de
push hl
ldh a, [hVBlank]
and 7
ld e, a
ld d, 0
ld hl, .VBlanks
add hl, de
add hl, de
ld a, [hli]
ld h, [hl]
ld l, a
call _hl_
call GameTimer
pop hl
pop de
pop bc
pop af
reti
.VBlanks:
dw VBlank0
dw VBlank1
dw VBlank2
dw VBlank3
dw VBlank4
dw VBlank5
dw VBlank6
dw VBlank0 ; just in case
VBlank0::
; normal operation
; rng
; scx, scy, wy, wx
; bg map buffer
; palettes
; dma transfer
; bg map
; tiles
; oam
; joypad
; sound
; inc frame counter
ld hl, hVBlankCounter
inc [hl]
; advance random variables
ldh a, [rDIV]
ld b, a
ldh a, [hRandomAdd]
adc b
ldh [hRandomAdd], a
ldh a, [rDIV]
ld b, a
ldh a, [hRandomSub]
sbc b
ldh [hRandomSub], a
ldh a, [hROMBank]
ldh [hROMBankBackup], a
ldh a, [hSCX]
ldh [rSCX], a
ldh a, [hSCY]
ldh [rSCY], a
ldh a, [hWY]
ldh [rWY], a
ldh a, [hWX]
ldh [rWX], a
; There's only time to call one of these in one vblank.
; Calls are in order of priority.
call UpdateBGMapBuffer
jr c, .done
call UpdatePalsIfCGB
jr c, .done
call DMATransfer
jr c, .done
call UpdateBGMap
; These have their own timing checks.
call Serve2bppRequest
call Serve1bppRequest
call AnimateTileset
.done
ldh a, [hOAMUpdate]
and a
jr nz, .done_oam
call hTransferShadowOAM
.done_oam
; vblank-sensitive operations are done
xor a
ld [wVBlankOccurred], a
ld a, [wOverworldDelay]
and a
jr z, .ok
dec a
ld [wOverworldDelay], a
.ok
ld a, [wTextDelayFrames]
and a
jr z, .ok2
dec a
ld [wTextDelayFrames], a
.ok2
call UpdateJoypad
ld a, BANK(_UpdateSound)
rst Bankswitch
call _UpdateSound
ldh a, [hROMBankBackup]
rst Bankswitch
ldh a, [hSeconds]
ldh [hUnusedBackup], a
ret
VBlank2::
; sound only
ldh a, [hROMBank]
ldh [hROMBankBackup], a
ld a, BANK(_UpdateSound)
rst Bankswitch
call _UpdateSound
ldh a, [hROMBankBackup]
rst Bankswitch
xor a
ld [wVBlankOccurred], a
ret
VBlank1::
; scx, scy
; palettes
; bg map
; tiles
; oam
; sound / lcd stat
ldh a, [hROMBank]
ldh [hROMBankBackup], a
ldh a, [hSCX]
ldh [rSCX], a
ldh a, [hSCY]
ldh [rSCY], a
call UpdatePals
jr c, .done
call UpdateBGMap
call Serve2bppRequest_VBlank
call hTransferShadowOAM
.done
xor a
ld [wVBlankOccurred], a
; get requested ints
ldh a, [rIF]
ld b, a
; discard requested ints
xor a
ldh [rIF], a
; enable lcd stat
ld a, 1 << LCD_STAT
ldh [rIE], a
; rerequest serial int if applicable (still disabled)
; request lcd stat
ld a, b
and 1 << SERIAL
or 1 << LCD_STAT
ldh [rIF], a
ei
ld a, BANK(_UpdateSound)
rst Bankswitch
call _UpdateSound
ldh a, [hROMBankBackup]
rst Bankswitch
di
; get requested ints
ldh a, [rIF]
ld b, a
; discard requested ints
xor a
ldh [rIF], a
; enable ints besides joypad
ld a, IE_DEFAULT
ldh [rIE], a
; rerequest ints
ld a, b
ldh [rIF], a
ret
UpdatePals::
; update pals for either dmg or cgb
ldh a, [hCGB]
and a
jp nz, UpdateCGBPals
; update gb pals
ld a, [wBGP]
ldh [rBGP], a
ld a, [wOBP0]
ldh [rOBP0], a
ld a, [wOBP1]
ldh [rOBP1], a
and a
ret
VBlank3::
; scx, scy
; palettes
; bg map
; tiles
; oam
; sound / lcd stat
ldh a, [hROMBank]
ldh [hROMBankBackup], a
ldh a, [hSCX]
ldh [rSCX], a
ldh a, [hSCY]
ldh [rSCY], a
ldh a, [hCGBPalUpdate]
and a
call nz, ForceUpdateCGBPals
jr c, .done
call UpdateBGMap
call Serve2bppRequest_VBlank
call hTransferShadowOAM
.done
xor a
ld [wVBlankOccurred], a
ldh a, [rIF]
push af
xor a
ldh [rIF], a
ld a, 1 << LCD_STAT
ldh [rIE], a
ldh [rIF], a
ei
ld a, BANK(_UpdateSound)
rst Bankswitch
call _UpdateSound
ldh a, [hROMBankBackup]
rst Bankswitch
di
; request lcdstat
ldh a, [rIF]
ld b, a
; and any other ints
pop af
or b
ld b, a
; reset ints
xor a
ldh [rIF], a
; enable ints besides joypad
ld a, IE_DEFAULT
ldh [rIE], a
; request ints
ld a, b
ldh [rIF], a
ret
VBlank4::
; bg map
; tiles
; oam
; joypad
; serial
; sound
ldh a, [hROMBank]
ldh [hROMBankBackup], a
call UpdateBGMap
call Serve2bppRequest
call hTransferShadowOAM
call UpdateJoypad
xor a
ld [wVBlankOccurred], a
call AskSerial
ld a, BANK(_UpdateSound)
rst Bankswitch
call _UpdateSound
ldh a, [hROMBankBackup]
rst Bankswitch
ret
VBlank5::
; scx
; palettes
; bg map
; tiles
; joypad
;
ldh a, [hROMBank]
ldh [hROMBankBackup], a
ldh a, [hSCX]
ldh [rSCX], a
call UpdatePalsIfCGB
jr c, .done
call UpdateBGMap
call Serve2bppRequest
.done
xor a
ld [wVBlankOccurred], a
call UpdateJoypad
xor a
ldh [rIF], a
ld a, 1 << LCD_STAT
ldh [rIE], a
; request lcd stat
ldh [rIF], a
ei
ld a, BANK(_UpdateSound)
rst Bankswitch
call _UpdateSound
ldh a, [hROMBankBackup]
rst Bankswitch
di
xor a
ldh [rIF], a
; enable ints besides joypad
ld a, IE_DEFAULT
ldh [rIE], a
ret
VBlank6::
; palettes
; tiles
; dma transfer
; sound
ldh a, [hROMBank]
ldh [hROMBankBackup], a
; inc frame counter
ld hl, hVBlankCounter
inc [hl]
call UpdateCGBPals
jr c, .done
call Serve2bppRequest
call Serve1bppRequest
call DMATransfer
.done
xor a
ld [wVBlankOccurred], a
ld a, BANK(_UpdateSound)
rst Bankswitch
call _UpdateSound
ldh a, [hROMBankBackup]
rst Bankswitch
ret

459
home/video.asm Normal file
View file

@ -0,0 +1,459 @@
; Functions dealing with VRAM.
DMATransfer::
; Return carry if the transfer is completed.
ldh a, [hDMATransfer]
and a
ret z
; Start transfer
ldh [rHDMA5], a
; Execution is halted until the transfer is complete.
xor a
ldh [hDMATransfer], a
scf
ret
UpdateBGMapBuffer::
; Copy [hBGMapTileCount] 16x8 tiles from wBGMapBuffer
; to bg map addresses in wBGMapBufferPointers.
; [hBGMapTileCount] must be even since this is done in pairs.
; Return carry on success.
ldh a, [hBGMapUpdate]
and a
ret z
ldh a, [rVBK]
push af
; Relocate the stack pointer to wBGMapBufferPointers
ld [hSPBuffer], sp
ld hl, wBGMapBufferPointers
ld sp, hl
; We can now pop the addresses of affected spots on the BG Map
ld hl, wBGMapPalBuffer
ld de, wBGMapBuffer
.next
; Copy a pair of 16x8 blocks (one 16x16 block)
rept 2
; Get our BG Map address
pop bc
; Palettes
ld a, 1
ldh [rVBK], a
ld a, [hli]
ld [bc], a
inc c
ld a, [hli]
ld [bc], a
dec c
; Tiles
xor a
ldh [rVBK], a
ld a, [de]
inc de
ld [bc], a
inc c
ld a, [de]
inc de
ld [bc], a
endr
; We've done 2 16x8 blocks
ldh a, [hBGMapTileCount]
dec a
dec a
ldh [hBGMapTileCount], a
jr nz, .next
; Restore the stack pointer
ldh a, [hSPBuffer]
ld l, a
ldh a, [hSPBuffer + 1]
ld h, a
ld sp, hl
pop af
ldh [rVBK], a
xor a
ldh [hBGMapUpdate], a
scf
ret
WaitTop::
; Wait until the top third of the BG Map is being updated.
ldh a, [hBGMapMode]
and a
ret z
ldh a, [hBGMapThird]
and a
jr z, .done
call DelayFrame
jr WaitTop
.done
xor a
ldh [hBGMapMode], a
ret
UpdateBGMap::
; Update the BG Map, in thirds, from wTilemap and wAttrmap.
ldh a, [hBGMapMode]
and a ; 0
ret z
; BG Map 0
dec a ; 1
jr z, .Tiles
dec a ; 2
jr z, .Attr
; BG Map 1
dec a ; useless
ldh a, [hBGMapAddress]
ld l, a
ldh a, [hBGMapAddress + 1]
ld h, a
push hl
xor a ; LOW(vBGMap1)
ldh [hBGMapAddress], a
ld a, HIGH(vBGMap1)
ldh [hBGMapAddress + 1], a
ldh a, [hBGMapMode]
push af
cp 3
call z, .Tiles
pop af
cp 4
call z, .Attr
pop hl
ld a, l
ldh [hBGMapAddress], a
ld a, h
ldh [hBGMapAddress + 1], a
ret
.Attr:
ld a, 1
ldh [rVBK], a
hlcoord 0, 0, wAttrmap
call .update
xor a
ldh [rVBK], a
ret
.Tiles:
hlcoord 0, 0
.update
ld [hSPBuffer], sp
; Which third?
ldh a, [hBGMapThird]
and a ; 0
jr z, .top
dec a ; 1
jr z, .middle
; 2
DEF THIRD_HEIGHT EQU SCREEN_HEIGHT / 3
; bottom
ld de, 2 * THIRD_HEIGHT * SCREEN_WIDTH
add hl, de
ld sp, hl
ldh a, [hBGMapAddress + 1]
ld h, a
ldh a, [hBGMapAddress]
ld l, a
ld de, 2 * THIRD_HEIGHT * BG_MAP_WIDTH
add hl, de
; Next time: top third
xor a
jr .start
.middle
ld de, THIRD_HEIGHT * SCREEN_WIDTH
add hl, de
ld sp, hl
ldh a, [hBGMapAddress + 1]
ld h, a
ldh a, [hBGMapAddress]
ld l, a
ld de, THIRD_HEIGHT * BG_MAP_WIDTH
add hl, de
; Next time: bottom third
ld a, 2
jr .start
.top
ld sp, hl
ldh a, [hBGMapAddress + 1]
ld h, a
ldh a, [hBGMapAddress]
ld l, a
; Next time: middle third
ld a, 1
.start
; Which third to update next time
ldh [hBGMapThird], a
; Rows of tiles in a third
ld a, THIRD_HEIGHT
; Discrepancy between wTilemap and BGMap
ld bc, BG_MAP_WIDTH - (SCREEN_WIDTH - 1)
.row
; Copy a row of 20 tiles
rept SCREEN_WIDTH / 2 - 1
pop de
ld [hl], e
inc l
ld [hl], d
inc l
endr
pop de
ld [hl], e
inc l
ld [hl], d
add hl, bc
dec a
jr nz, .row
ldh a, [hSPBuffer]
ld l, a
ldh a, [hSPBuffer + 1]
ld h, a
ld sp, hl
ret
Serve1bppRequest::
; Only call during the first fifth of VBlank
ld a, [wRequested1bppSize]
and a
ret z
; Back out if we're too far into VBlank
ldh a, [rLY]
cp LY_VBLANK
ret c
cp LY_VBLANK + 2
ret nc
; Copy [wRequested1bppSize] 1bpp tiles from [wRequested1bppSource] to [wRequested1bppDest]
ld [hSPBuffer], sp
; Source
ld hl, wRequested1bppSource
ld a, [hli]
ld h, [hl]
ld l, a
ld sp, hl
; Destination
ld hl, wRequested1bppDest
ld a, [hli]
ld h, [hl]
ld l, a
; # tiles to copy
ld a, [wRequested1bppSize]
ld b, a
xor a
ld [wRequested1bppSize], a
.next
rept 3
pop de
ld [hl], e
inc l
ld [hl], e
inc l
ld [hl], d
inc l
ld [hl], d
inc l
endr
pop de
ld [hl], e
inc l
ld [hl], e
inc l
ld [hl], d
inc l
ld [hl], d
inc hl
dec b
jr nz, .next
ld a, l
ld [wRequested1bppDest], a
ld a, h
ld [wRequested1bppDest + 1], a
ld [wRequested1bppSource], sp
ldh a, [hSPBuffer]
ld l, a
ldh a, [hSPBuffer + 1]
ld h, a
ld sp, hl
ret
Serve2bppRequest::
; Only call during the first fifth of VBlank
ld a, [wRequested2bppSize]
and a
ret z
; Back out if we're too far into VBlank
ldh a, [rLY]
cp LY_VBLANK
ret c
cp LY_VBLANK + 2
ret nc
jr _Serve2bppRequest
Serve2bppRequest_VBlank::
ld a, [wRequested2bppSize]
and a
ret z
_Serve2bppRequest::
; Copy [wRequested2bppSize] 2bpp tiles from [wRequested2bppSource] to [wRequested2bppDest]
ld [hSPBuffer], sp
; Source
ld hl, wRequested2bppSource
ld a, [hli]
ld h, [hl]
ld l, a
ld sp, hl
; Destination
ld hl, wRequested2bppDest
ld a, [hli]
ld h, [hl]
ld l, a
; # tiles to copy
ld a, [wRequested2bppSize]
ld b, a
xor a
ld [wRequested2bppSize], a
.next
rept 7
pop de
ld [hl], e
inc l
ld [hl], d
inc l
endr
pop de
ld [hl], e
inc l
ld [hl], d
inc hl
dec b
jr nz, .next
ld a, l
ld [wRequested2bppDest], a
ld a, h
ld [wRequested2bppDest + 1], a
ld [wRequested2bppSource], sp
ldh a, [hSPBuffer]
ld l, a
ldh a, [hSPBuffer + 1]
ld h, a
ld sp, hl
ret
AnimateTileset::
; Only call during the first fifth of VBlank
ldh a, [hMapAnims]
and a
ret z
; Back out if we're too far into VBlank
ldh a, [rLY]
cp LY_VBLANK
ret c
cp LY_VBLANK + 7
ret nc
ldh a, [hROMBank]
push af
ld a, BANK(_AnimateTileset)
rst Bankswitch
ldh a, [rSVBK]
push af
ld a, BANK(wTilesetAnim)
ldh [rSVBK], a
ldh a, [rVBK]
push af
xor a
ldh [rVBK], a
call _AnimateTileset
pop af
ldh [rVBK], a
pop af
ldh [rSVBK], a
pop af
rst Bankswitch
ret

93
home/window.asm Normal file
View file

@ -0,0 +1,93 @@
RefreshScreen::
call ClearWindowData
ldh a, [hROMBank]
push af
ld a, BANK(ReanchorBGMap_NoOAMUpdate) ; aka BANK(LoadFonts_NoOAMUpdate)
rst Bankswitch
call ReanchorBGMap_NoOAMUpdate
call _OpenAndCloseMenu_HDMATransferTilemapAndAttrmap
call LoadFonts_NoOAMUpdate
pop af
rst Bankswitch
ret
CloseText::
ldh a, [hOAMUpdate]
push af
ld a, $1
ldh [hOAMUpdate], a
call .CloseText
pop af
ldh [hOAMUpdate], a
ld hl, wVramState
res 6, [hl]
ret
.CloseText:
call ClearWindowData
xor a
ldh [hBGMapMode], a
call OverworldTextModeSwitch
call _OpenAndCloseMenu_HDMATransferTilemapAndAttrmap
xor a
ldh [hBGMapMode], a
call SafeUpdateSprites
ld a, $90
ldh [hWY], a
call UpdatePlayerSprite
farcall InitMapNameSign
farcall LoadOverworldFont
ret
OpenText::
call ClearWindowData
ldh a, [hROMBank]
push af
ld a, BANK(ReanchorBGMap_NoOAMUpdate) ; aka BANK(LoadFonts_NoOAMUpdate)
rst Bankswitch
call ReanchorBGMap_NoOAMUpdate ; clear bgmap
call SpeechTextbox
call _OpenAndCloseMenu_HDMATransferTilemapAndAttrmap ; anchor bgmap
call LoadFonts_NoOAMUpdate ; load font
pop af
rst Bankswitch
ret
_OpenAndCloseMenu_HDMATransferTilemapAndAttrmap::
ldh a, [hOAMUpdate]
push af
ld a, $1
ldh [hOAMUpdate], a
farcall OpenAndCloseMenu_HDMATransferTilemapAndAttrmap
pop af
ldh [hOAMUpdate], a
ret
SafeUpdateSprites::
ldh a, [hOAMUpdate]
push af
ldh a, [hBGMapMode]
push af
xor a
ldh [hBGMapMode], a
ld a, $1
ldh [hOAMUpdate], a
call UpdateSprites
xor a
ldh [hOAMUpdate], a
call DelayFrame
pop af
ldh [hBGMapMode], a
pop af
ldh [hOAMUpdate], a
ret