Option to show shiny colors in Pokédex - pret/pokecrystal GitHub Wiki
This implementation was adapted from Crystal Clear. It requires only one WRAM byte. We'll also be using a single bit in order to handle the current status, leaving seven more free for potential future features.
Fortunately, there is space for this in the existing Pokédex structure. For posterity's sake, we'll also clean up the related 1.1 differences. You can quickly find these two spots in ram/wram.asm by searching for _CRYSTAL11
in your editor of choice.
Contents
- Create the Pokédex Shiny toggle
- Change palette depending on toggle state
- Trigger toggle with SELECT button
1. Create the Pokédex shiny toggle
Edit ram/wram.asm:
...
wBackupDexListingCursor:: db
wBackupDexListingPage:: db
wDexCurLocation:: db
-if DEF(_CRYSTAL11)
wPokedexStatus:: db
+wPokedexShinyToggle::
+; bit 0: set if displaying shiny palettes
+ db
wPokedexDataEnd::
-else
-wPokedexDataEnd:: ds 1
-endc
- ds 2
+ ds 1
NEXTU
...
Since we removed the 1.1 differences in the first section, we'll want to do the same here. In the same file:
...
NEXTU
; pokedex
wPrevDexEntryJumptableIndex:: db
-if DEF(_CRYSTAL11)
wPrevDexEntryBackup:: db
-else
-wPrevDexEntryBackup::
-wPokedexStatus:: db
-endc
wUnusedPokedexByte:: db
NEXTU
...
2. Change palette depending on toggle state
Now that we've got a byte, we need to locate where the engine loads the selected Pokémon palette. Inspecting engine/pokedex/pokedex.asm reveals the SCGB_POKEDEX
constant, so we'll need to modify the cooresponding CGB layout.
Within _CGB_Pokedex
, we can see that is uses the function GetMonPalettePointer
, in engine/gfx/color.asm. Simply put, this function returns the pointer of a corresponding Pokémon palette in register hl
, based on the species value in register a
.
The way that palette data is aligned within Pokémon Crystal is convenient. Each Pokémon's default palette is four bytes, and the next four bytes after are the shiny palette.
With this in mind, the following implementation should be easier to understand. Whenever _CGB_Pokedex
is called, the default palette is always loaded. Then, we check bit 0 of wPokedexShinyToggle
, and if it's not set (jr z
), we jump ahead to loading the palette at hl
. But if it is set, we'll increment the value at hl
4 times, which will put us at the shiny palette!
Edit _CGB_Pokedex
in engine/gfx/cgb_layouts.asm:
_CGB_Pokedex:
ld de, wBGPals1
ld a, PREDEFPAL_POKEDEX
call GetPredefPal
call LoadHLPaletteIntoDE ; dex interface palette
ld a, [wCurPartySpecies]
cp $ff
jr nz, .is_pokemon
ld hl, .PokedexQuestionMarkPalette
call LoadHLPaletteIntoDE ; green question mark palette
jr .got_palette
.is_pokemon
call GetMonPalettePointer
+ ld a, [wPokedexShinyToggle]
+ and a
+ jr z, .not_shiny
+ ; Get shiny palette pointer
+ inc hl
+ inc hl
+ inc hl
+ inc hl
+.not_shiny
call LoadPalette_White_Col1_Col2_Black ; mon palette
.got_palette
...
3. Trigger toggle with SELECT button
Now we have a WRAM byte, and code that uses it. All that's left is a way to set and clear the bit we're using.
The quickest method is to take advantage of the fact that within the Pokédex, the SELECT
button is unused when viewing a Pokédex entry. We can see that the joypad handling for this screen is in Pokedex_UpdateDexEntryScreen
. We'll want to add the SELECT
button handling first.
Edit Pokedex_UpdateDexEntryScreen
in engine/pokedex/pokedex.asm:
Pokedex_UpdateDexEntryScreen:
ld de, DexEntryScreen_ArrowCursorData
call Pokedex_MoveArrowCursor
ld hl, hJoyPressed
ld a, [hl]
and B_BUTTON
jr nz, .return_to_prev_screen
ld a, [hl]
and A_BUTTON
jr nz, .do_menu_action
+ ld a, [hl]
+ and SELECT
+ jr nz, .toggle_shininess
call Pokedex_NextOrPreviousDexEntry
ret nc
call Pokedex_IncrementDexPointer
ret
Then, at the end of the function, we'll add some code. First, we want to toggle the setting. So we check the wPokedexShinyToggle
bit, and if it's not set, we'll set it and update the palettes. Otherwise if it's set, we'll clear the bit before updating the palettes.
Edit Pokedex_UpdateDexEntryScreen
in engine/pokedex/pokedex.asm:
.max_volume
call MaxVolume
ld a, [wPrevDexEntryJumptableIndex]
ld [wJumptableIndex], a
ret
+.toggle_shininess
+; toggle the current shininess setting
+ ld a, [wPokedexShinyToggle]
+ xor 1
+ ld [wPokedexShinyToggle], a
+ ; refresh palettes
+ ld a, SCGB_POKEDEX
+ call Pokedex_GetSGBLayout
+ ; play sound based on setting
+ ld a, [wPokedexShinyToggle]
+ and a
+ ld de, SFX_BUMP
+ jr z, .got_sound
+ ld de, SFX_SHINE
+.got_sound
+ call PlaySFX
+ jp WaitSFX
While not strictly a requirement, there's some additional code here for some audio feedback when the SELECT
button is pressed, based on the setting. You're free to use any SFX that you like, or if you want none at all, you can do this:
.toggle_shininess
; toggle the current shininess setting
ld a, [wPokedexShinyToggle]
xor 1
ld [wPokedexShinyToggle], a
; refresh palettes
ld a, SCGB_POKEDEX
- call Pokedex_GetSGBLayout
- ; play sound based on setting
- ld a, [wPokedexShinyToggle]
- and a
- ld de, SFX_BUMP
- jr z, .got_sound
- ld de, SFX_SHINE
-.got_sound
- call PlaySFX
- jp WaitSFX
+ jp Pokedex_GetSGBLayout
Before we finish, we need to clear the toggle before exiting the Pokédex or else it'll show a Shiny entry whenever we get a new Pokémon (whether it's Shiny or not). In the same file, go and edit Pokedex
:
Pokedex:
...
.exit
ld de, SFX_READ_TEXT_2
call PlaySFX
call WaitSFX
call ClearSprites
ld a, [wCurDexMode]
ld [wLastDexMode], a
+ xor a
+ ld [wPokedexShinyToggle], a
pop af
ldh [hInMenu], a
...
And there you go! Our new Pokédex Shiny toggle is completely functional!