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

  1. Create the Pokédex Shiny toggle
  2. Change palette depending on toggle state
  3. 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!

Screenshot