Shiny Pokemon (SGB Users Only) - pret/pokered GitHub Wiki
Greetings! With this tutorial, you'll be able to introduce shiny Pokemon into your own Gen 1 ROMhack. I've opted to go with the Gen 2 method of using DVs to determine shininess. There's a LOT of stuff to do with adding addresses, writing new routines, and adjusting existing routines, so let's get right into it.
Note: This change will only be noticeable if playing on SGB (Super GameBoy) or emulating using a SGB core. If you don't (or can't) use a SGB (core), I'm afraid there's not much point in going any further.
We'll be adding 4 new named addresses to ram\wram.asm to hold data that the game will use to determine shininess in various sprite-viewing scenarios. The addresses are as follows:
-
wShinyMon: used to determine whether to load a normal or shiny Mon sprite -
wShinyBattleMon: used exclusively to determine whether to draw "shiny stars" on the player Mon's battle HUD -
wTradeOccurring: used to display shiny sprites during in-game trades (and also when viewing the Hall of Fame using the PC) -
wPlayerTradedMonDVs: used exclusviely to hold the DVs of player's outgoing traded Mon
Where you insert the addresses is largely unimportant, save for one important caveat:
This range of values is what gets zero-ed out when selecting "New Game." We want our new addresses reset to zero upon starting a new game or else you might get some issues displaying shinies after beginning a new save file.
For reference, here's what I did for the "trade" addresses:
wMovementFlags:: db
wCompletedInGameTradeFlags:: dw
-
- ds 2
-
+; usually 0, used to display shiny sprites during in-game trades
+; set to 1 before displaying outgoing player Mon's sprite
+; set to 2 before displaying incoming NPC Mon's sprite
+; reset to 0 after displaying incoming NPC Mon's sprite
+wTradeOccurring:: db
+wPlayerTradedMonDVs::db ; holds the DVs of player's outgoing traded Mon
+ ds 1 ; don't remove, holds Speed and Special DVs for traded Mon
wWarpedFromWhichWarp:: db
wWarpedFromWhichMap:: db
- ds 2
+ ds 1
wCardKeyDoorY:: db
wCardKeyDoorX:: db
ds 2
wFirstLockTrashCanIndex:: db
wSecondLockTrashCanIndex:: db
And here's what I did for the "ShinyMon" addresses:
; $00 = walking
; $01 = biking
; $02 = surfing
wWalkBikeSurfState:: db
- ds 10
+; 0 = standard palette
+; 1 = shiny palette
+wShinyMon:: db
+; used to load shiny stars on BattleMon's HUD
+wShinyBattleMon:: db
+
+ ds 8
wTownVisitedFlag:: flag_array NUM_CITY_MAPSAddress additions are done. Now for a routine to check shininess:
Here I'll give two versions of a CheckForShinyMon routine. The first will use the Gen2 formula (giving the infamous "1 in 8,192" shiny odds), the second will allow you to tweak shiny odds to your liking. Regardless of the version you use, I recommend inserting the routine just ahead of InitPartyMenuBlkPacket.
In Gen 2, a Pokemon is shiny only if its Defense, Speed, and Special DVs are all EXACTLY 10, with the Attack DV being 1 of 8 possible values. To implement this system, use the following routine:
...
add hl, de
ld a, [hl]
ret
+CheckForShinyMon:
+; given a Mon's DVs in hl, alters the value in wShinyMon
+; wShinyMon = 1 loads a shiny palette
+; wShinyMon = 0 loads a standard palette
+ xor a ; set 'a' to 0
+ ld [wShinyMon], a ; initialize to standard palette value
+ push hl ; store Mon's ATK/DEF DVs to be recalled later
+ ld a, [hl] ; 'a' = ATK/DEF DVs
+ swap a ; moves ATK DV to low nybble
+ and $0f ; isolates ATK DV
+ ld b, a ; 'b' = ATK DV
+ ld hl, ShinyAttackDVValues
+.checkAttackDVLoop
+ ld a, [hli] ; 'a' = current 'ShinyAttackDVValues' list entry
+ cp -1 ; did we reach end of list with no success?
+ jr z, .notShinyMon ; exit if so
+ cp b ; compare list entry to ATK DV
+ jr nz, .checkAttackDVLoop ; loop back if no match
+; proceed if valid Attack DV was found
+ pop hl ; restore ATK/DEF DV values
+; get Defense DV
+ ld a, [hli] ; 'a' = ATK/DEF DVs, 'hl' = SPD/SPC DVs
+ call IsolateDVAndCheckIfShinyValue ; DEF DV already low nybble, no swap needed
+ ret nz ; exit if DEF DV != 10
+; get Speed DV
+ ld a, [hl] ; 'a' = SPD/SPC DVs
+ swap a ; moves SPD DV to low nybble
+ call IsolateDVAndCheckIfShinyValue
+ ret nz ; exit if SPD DV != 10
+; get Special DV
+ ld a, [hl] ; 'a' = SPD/SPC DVs
+ call IsolateDVAndCheckIfShinyValue ; SPC DV already low nybble, no swap needed
+ ret nz ; exit if SPC DV != 10
+; making it here means all DVs matched shiny values
+ ld a, 1
+ ld [wShinyMon], a ; alter wShinyMon to load shiny palette and exit
+ ret
+.notShinyMon
+ pop hl ; clears DVs from stack prior to exit
+ ret
+
+ShinyAttackDVValues: ; these are identical to the acceptable Attack DVs in Gen 2
+ db $2
+ db $3
+ db $6
+ db $7
+ db $a
+ db $b
+ db $e
+ db $f
+ db -1 ; end
+
+IsolateDVAndCheckIfShinyValue:
+ and $0f
+ cp $a
+ ret
+
InitPartyMenuBlkPacket:
...If you wish to be more flexible with the allowed shiny DV values, ignore the above version and try this routine instead:
...
add hl, de
ld a, [hl]
ret
+CheckForShinyMon:
+; given a Mon's DVs in hl, alters the value in wShinyMon
+; wShinyMon = 1 loads a shiny palette
+; wShinyMon = 0 loads a standard palette
+ xor a
+ ld [wShinyMon], a ; initalizes to standard palette
+ ld c, a ; 'c' will be used to determine next stat to check
+ push hl ; stores ATK/DEF DV values
+ ld a, [hl] ; loads Mon's ATK/DEF DVs into 'a'
+ swap a ; moves ATK DV to low nybble
+.getListOfShinyValues
+ and $0f ; isolates DV
+ ld b, a ; moves DV to register 'b'
+ ld hl, ShinyDVValues
+.checkDVLoop
+ ld a, [hli]
+ cp -1 ; did we reach end of list with no success?
+ jr z, .notShinyMon ; exit if so
+ cp b ; checks if DV matches any shiny values
+ jr nz, .checkDVLoop ; if no match, loop back to check next value
+; proceed if valid shiny DV was found
+ pop hl ; restores current DV values
+ inc c ; 'c' will equal 1, 2, 3, or 4
+ ld a, c
+ cp 1
+ jr z, .findDefenseDV
+ cp 2
+ jr z, .findSpeedDV
+ cp 3
+ jr z, .findSpecialDV
+ ld a, 1 ; making it here means all DVs are valid shiny values
+ ld [wShinyMon], a ; alter wShinyMon to load shiny palette and exit
+ ret
+.findDefenseDV
+ ld a, [hli] ; 'a' = ATK/DEF DVs, 'hl' = SPD/SPC DVs
+ push hl ; stores SPD/SPC DV values
+ jr .getListOfShinyValues
+.findSpeedDV
+ ld a, [hl] ; loads SPD/SPC DV values into 'a'
+ push hl ; stores SPD/SPC values
+ swap a ; moves SPD DV to low nybble
+ jr .getListOfShinyValues
+.findSpecialDV
+ ld a, [hl] ; loads SPD/SPC DV values into 'a'
+ push hl ; just ensures any following instance of 'pop hl' works correctly
+ jr .getListOfShinyValues
+.notShinyMon
+ pop hl ; clears DV values from stack prior to exit
+ ret
+
+; 4 valid values per DV = 1/256 chance of a shiny appearing
+ShinyDVValues:
+ db $f
+ db $a
+ db $5
+ db $0
+ db -1 ; end
+
InitPartyMenuBlkPacket:
...Of course, you can edit the ShinyDVValues list to your liking. Change the acceptable values, remove values from the list to reduce shiny odds, add values to the list to increase shiny odds... it's all up to you.
Whichever routine you choose to implement, we now have a way to alter our new wShinyMon address. Before putting that address to work, however, consider the following:
This is helpful to check how your shiny Pokemon will look when in battle and while viewing the status screen. It's simple enough to implement... go to engine\debug\debug_party.asm and insert the following:
...
; Pikachu gets Surf.
ld hl, wPartyMon6Moves + 2
ld a, SURF
ld [hl], a
ld hl, wPartyMon6PP + 2
ld a, 15
ld [hl], a
+ ; Exeggutor gets shiny DVs
+ ld hl, wPartyMon1DVs
+ ld a, $fa ; these will be the ATK/DEF DVs... adjust these based on your own implemented shiny values
+ ld [hli], a
+ ld a, $aa ; and these will be the SPD/SPC DVs... adjust as needed
+ ld [hl], a
+
; Get some debug items.
ld hl, wNumBagItems
ld de, DebugNewGameItemsList
...If you want more team members to be shiny, you can Copy+Paste that and substitute wPartyMon[X]DVs in place of wPartyMon1DVs, with [X] being the team slot you want to change to a shiny.
Additionally, if you want trainers to have shiny Pokemon, simply add the values $8 and $9 to your ShinyDVValues list (assuming you haven't modified the fixed trainer DVs, or course). All trainer Pokemon have fixed DVs of $9888 in the vanilla game.
If you want shiny wild Mons, my only suggestion is to add tons of different values to your ShinyDVValues list to greatly increase the chances of finding a shiny.
When you're satisfied with your testing, don't forget to edit ShinyDVValues to only contain your intended values!
Go to data\pokemon\palettes.asm. My suggestion is to highlight and copy the ENTIRE MonsterPalettes list, then Paste it at the end so you have TWO identical instances of the list. Next, change the name of the second list to ShinyMonsterPalettes, as seen below:
...
db PAL_MEWMON ; MEWTWO
db PAL_MEWMON ; MEW
assert_table_length NUM_POKEMON + 1
-MonsterPalettes:
+ShinyMonsterPalettes:
table_width 1
db PAL_MEWMON ; MISSINGNO
...Now this part is up to you and your tastes... you'll need go through the ShinyMonsterPalettes list and assign a new palette to every Pokemon to tell the game what colors to give to the shiny versions of each Pokemon.
NOTE: DO NOT adjust the PAL_MEWMON entry for Missingno. on the ShinyMonsterPalettes: list (or on the standard MonsterPalettes: list, for that matter). Trainer sprites use the Missingno. slot to determine the color palette to load. If you mess with that specific entry, you'll get "shiny Trainers" if the game detects a shiny palette should be loaded. By leaving that value alone, the standard palette will always load for trainer sprites. Other entries using the PAL_MEWMON palette CAN be adjusted safely.
Now to put the wShinyMon address to work. Return to engine\gfx\palettes.asm and make the following edits:
Find SetPal_Battle:
SetPal_Battle:
ld hl, PalPacket_Empty
ld de, wPalPacket
ld bc, $10
call CopyData
+ push bc
+ ld hl, wBattleMonDVs
+ call CheckForShinyMon ; check if player Mon is shiny
+ ld a, [wShinyMon]
+ ld [wShinyBattleMon], a ; store this value for drawing shiny stars on the player HUD
+ pop bc
ld a, [wPlayerBattleStatus3]
ld hl, wBattleMonSpecies
call DeterminePaletteID
ld b, a
+ push bc
+ ld hl, wEnemyMonDVs
+ ld a, [wEnemyMonSpecies2]
+ cp RESTLESS_SOUL ; prevent Ghost Marowak being shiny
+ ld a, 0
+ ld [wShinyMon], a
+ jr z, .skipShinyCheck
+ call CheckForShinyMon ; check if enemy Mon is shiny
+.skipShinyCheck
+ pop bc
ld a, [wEnemyBattleStatus3]
...Find SetPal_StatusScreen:
SetPal_StatusScreen:
ld hl, PalPacket_Empty
ld de, wPalPacket
ld bc, $10
call CopyData
ld a, [wCurPartySpecies]
cp NUM_POKEMON_INDEXES + 1
jr c, .pokemon
ld a, $1 ; not pokemon
+ jr .getPalette
.pokemon
+ push af ; saves [wCurPartySpecies] currently in 'a'
+ ld a, [wMiscFlags]
+ bit BIT_USING_GENERIC_PC, a ; check if in the PC or the START menu
+ ld a, [wWhichPokemon]
+ ld hl, wPartyMon1DVs
+ ld bc, wPartyMon2 - wPartyMon1
+ jr z, .getDVsAndCheckIfShiny ; use PartyMonDVs if in START menu
+ ld hl, wBoxMon1DVs ; load BoxMonDVs if in PC
+ ld bc, wBoxMon2 - wBoxMon1
+.getDVsAndCheckIfShiny
+ call AddNTimes
+ call CheckForShinyMon
+ pop af ; restores [wCurPartySpecies] to 'a'
+.getPalette
call DeterminePaletteIDOutOfBattle
push af
...Find SetPal_PokemonWholeScreen:
SetPal_PokemonWholeScreen:
push bc
ld hl, PalPacket_Empty
ld de, wPalPacket
ld bc, $10
call CopyData
pop bc
ld a, c
and a
ld a, PAL_BLACK
jr nz, .next
ld a, [wWholeScreenPaletteMonSpecies]
+ push af ; stores [wWholeScreenPaletteMonSpecies]
+ ld a, [wTradeOccurring] ; checks if in-game trade is occurring or if viewing Hall of Fame
+ and a
+ jr z, .getMonTeamslot ; jump if not in a trade or not viewing Hall of Fame
+ cp 1
+ jr z, .getOutgoingPlayerMonDVs ; jump if loading player's outgoing traded Mon's sprite
+ cp 2
+ ld a, 5 ; teamslot 6, the destination of the incoming traded Mon
+ jr z, .getMonDVs ; jump if loading NPC's incoming traded Mon's sprite
+ xor a ; if we make it here, we're viewing the HoF... set 'a' to 0
+ ld [wShinyMon], a ; force Mon sprites to be standard palette
+ jr .getPalette ; skips the usual shiny check
+.getOutgoingPlayerMonDVs
+ ld hl, wPlayerTradedMonDVs
+ jr .checkIfMonIsShiny
+.getMonTeamslot
+ ld a, [wWhichPokemon]
+.getMonDVs
+ ld hl, wPartyMon1DVs
+ ld bc, wPartyMon2 - wPartyMon1
+ call AddNTimes
+.checkIfMonIsShiny
+ call CheckForShinyMon
+.getPalette
+ pop af ; restores [wWholeScreenPaletteMonSpecies]
call DeterminePaletteIDOutOfBattle
.next
ld [wPalPacket + 1], a
ld hl, wPalPacket
ld de, BlkPacket_WholeScreen
retFind DeterminePaletteID: and move down to .skipDexNumConversion
...
.skipDexNumConversion
ld e, a
ld d, 0
+ ld a, [wShinyMon]
+ cp 1
+ ld hl, ShinyMonsterPalettes
+ jr z, .getListEntry
ld hl, MonsterPalettes ; not just for Pokemon, Trainers use it too
+.getListEntry
add hl, de
ld a, [hl]
retUnfortunately, due to data structure limitations I was unable to find a way to store shiny Mons in the HoF. Additionally, without the above check in SetPal_PokemonWholeScreen the shininess for the HoF sprites would actually be determined by the shininess of your party Pokemon. I figured that the best way to address this was to just force HoF Pokemon to have their normal palettes. To do this, open engine\menus\league_pc.asm and insert just two lines:
LeaguePCShowTeam:
ld c, PARTY_LENGTH
.loop
push bc
+ ld a, 3
+ ld [wTradeOccurring], a ; this address is also used to force non-shiny HoF sprites
call LeaguePCShowMon
...We now need to make sure that [wTradeOccurring] gets reset to zero, or else viewing the PC Hall of Fame causes shiny sprites to not be displayed specifically during evolution. Back up slightly (still in the same file) to the .doneShowingTeams section of the previous routine and add just one line:
...
.doneShowingTeams
pop af
ldh [hTileAnimations], a
pop af
ld [wUpdateSpritesEnabled], a
+ ld [wTradeOccurring], a ; resets [wTradeOccurring] to zero
pop hl
res BIT_NO_TEXT_DELAY, [hl]
...And that's that. Do note that, for what it's worth, shiny Pokemon CAN display their alternate coloration when they're being INDUCTED into the Hall of Fame (just before the credits roll). We'll cover that now.
Go to engine\movie\credits.asm and insert two lines right at the beginning:
HallOfFamePC:
+ xor a
+ ld [wWhichPokemon], a ; used to determine if any shiny Mons are entering HoF
farcall AnimateHallOfFame
call ClearScreen
...Next, go to engine\movie\hall_of_fame.asm, find HoFShowMonOrPlayer:, and go down to .next1
...
.next1
ld b, SET_PAL_POKEMON_WHOLE_SCREEN
ld c, 0
call RunPaletteCommand
+; increments wWhichPokemon to load next HoF Mon's DVs for shiny-checking
+ ld a, [wWhichPokemon] ; starts at 0, see 'engine\movie\credits.asm'
+ inc a ; moves to next team member
+ ld [wWhichPokemon], a ; stores value for next HoF Mon
ld a, %11100100
...Now your precious shinies will display all their off-color glory before the credits roll.
Next we adjust how trading works. First go to engine\events\in_game_trades.asm and find InGameTrade_DoTrade:
...
ld a, TRADETEXT_WRONG_MON
- jr nz, .tradeFailed ; jump if the selected mon's species is not the required one
+ jp nz, .tradeFailed ; jump if the selected mon's species is not the required one
ld a, [wWhichPokemon]
ld hl, wPartyMon1Level
ld bc, wPartyMon2 - wPartyMon1
call AddNTimes
ld a, [hl]
ld [wCurEnemyLevel], a
+ ld a, [wWhichPokemon] ; preserves outgoing player Mon's DVs for shiny checking
+ ld hl, wPartyMon1DVs
+ call AddNTimes ; 'bc' is still the same from earlier
+ ld a, [hli] ; loads ATK/DEF DVs
+ ld [wPlayerTradedMonDVs], a ; stores them
+ ld a, [hl] ; loads SPD/SPC DVs
+ ld [wPlayerTradedMonDVs + 1], a ; stores them
ld hl, wCompletedInGameTradeFlags
ld a, [wWhichTrade]
ld c, a
ld b, FLAG_SET
predef FlagActionPredef
ld hl, ConnectCableText
call PrintText
+ ld a, [wInGameTradeReceiveMonSpecies]
+ ld [wCurPartySpecies], a
+ xor a
+ ld [wMonDataLocation], a ; not used
+ ld [wRemoveMonFromBox], a
+ call RemovePokemon
+ ld a, $80 ; prevent the player from naming the mon
+ ld [wMonDataLocation], a
+ call AddPartyMon
ld a, [wWhichPokemon]
push af
ld a, [wCurEnemyLevel]
push af
call LoadHpBarAndStatusTilePatterns
call InGameTrade_PrepareTradeData
predef InternalClockTradeAnim
pop af
ld [wCurEnemyLevel], a
pop af
ld [wWhichPokemon], a
- ld a, [wInGameTradeReceiveMonSpecies]
- ld [wCurPartySpecies], a
- xor a
- ld [wMonDataLocation], a ; not used
- ld [wRemoveMonFromBox], a
- call RemovePokemon
- ld a, $80 ; prevent the player from naming the mon
- ld [wMonDataLocation], a
- call AddPartyMon
call InGameTrade_CopyDataToReceivedMon
...This will save your outgoing Mon's DVs, and it shifts the timing with which the game removes your traded Pokemon from your party and adds the NPC Pokemon in its stead. We want to be able to read the incoming Pokemon's DVs before its sprite is to be displayed, and the original order of events simply didn't allow for that. Honestly, it doesn't matter WHEN the party update occurs, so long as your original Pokemon is the party before the trade and the new Pokemon is in your party after the trade is completed.
Now go to engine\movie\trade.asm and find Trade_ShowPlayerMon:
...
call Trade_PrintPlayerMonInfoText
ld b, HIGH(vBGMap0)
call CopyScreenTileBufferToVRAM
call ClearScreen
+ ld a, 1
+ ld [wTradeOccurring], a
ld a, [wTradedPlayerMonSpecies]
call Trade_LoadMonSprite
...Stay in the same file and move down to Trade_ShowEnemyMon:
...
call Trade_CopyTileMapToVRAM
ld a, $1
ldh [hAutoBGTransferEnabled], a
+ inc a ; 'a' = 2
+ ld [wTradeOccurring], a
ld a, [wTradedEnemyMonSpecies]
call Trade_LoadMonSprite
ld a, TRADE_BALL_POOF_ANIM
call Trade_ShowAnimation
ld a, $1
ldh [hAutoBGTransferEnabled], a
ld a, [wTradedEnemyMonSpecies]
call PlayCry
+ xor a
+ ld [wTradeOccurring], a
call Trade_Delay100
...It also bears mentioning that in-game traded Pokemon come with totally random DVs... so there's a small chance the NPC might send you a shiny Pokemon.
Surprise!
With all that out of the way, we now have a rudimentary system that allows for shiny Pokemon sprites. You can stop here if you really want... or you can read on to see how we can add some visual polish to wild encounters, status screens, and the battle HUD.
With this bit, wild shiny encounters will cause the "Level-Up" jingle to play while displaying a text box saying, "What's this?" Go to engine\battle\common_text.asm and insert this:
PrintBeginningBattleText:
ld a, [wIsInBattle]
dec a
jr nz, .trainerBattle
ld a, [wCurMap]
cp POKEMON_TOWER_3F
jr c, .notPokemonTower
cp POKEMON_TOWER_7F + 1
jr c, .pokemonTower
.notPokemonTower
+ ld a, [wShinyMon]
+ and a
+ jr z, .standardBattleIntro
+ callfar DrawAllPokeballs
+ ld hl, WhatsThisText
+ call PrintText
+ ld a, SFX_LEVEL_UP
+ call PlaySound
+ call WaitForSoundToFinish
+ ld c, 20
+ call DelayFrames
+.standardBattleIntro
ld a, [wEnemyMonSpecies2]
call PlayCry
...Now, move on down in the same file and insert:
...
.done
ret
+WhatsThisText:
+ text_far _WhatsThisText
+ text_end
WildMonAppearedText:
text_far _WildMonAppearedText
text_end
...Then go to data\text\text_2.asm and define the text:
...
_GrewLevelText::
text_ram wNameBuffer
text " grew"
line "to level @"
text_decimal wCurEnemyLevel, 1, 3
text "!@"
text_end
+_WhatsThisText::
+ text "What's this?"
+ done
+
_WildMonAppearedText::
text "Wild @"
text_ram wEnemyMonNick
text_start
line "appeared!"
prompt
...This is what you'll see before the standard "Wild [Pokemon] appeared!" text:
That completes the special battle intro when encountering a wild shiny Pokemon. I opted NOT to create a similar special sequence when shinies are in trainer battles... it just ends up making battles feel sluggish having to wait for a flashy sequence every time a shiny comes out. To preserve that special, distinctive feeling, we'll make a small shiny star symbol (similar to the Gen 2 symbol) for the status screen and battle HUD.
Now to alter some game graphics...
Go to gfx\font\font.png. Open it using any image-editing program that'll let you work at the pixel-level... even something as simple as MS Paint will do. The image looks like this:
We're going to take the three unused Japanese characters and convert them into two different variants of the shiny star symbol. One variant will take up two tiles and will be used on the status screen (where the two-tile format will allow it to center nicely), while the other variant will take only one tile and will be used on the battle HUD (the one-tile format is good here, since space is limited).
Each star you draw will be composed of two intersecting lines, each 3 pixels in length. Distribute them across the tiles as seen in the following image:
NOTE: This probably goes without saying, but DO NOT include the red lines in your own edit. I only put them there to illustrate the tile borders. rgb.asm will have a conniption if you try to include actual colors in any sort of graphic like this one.
Having said that, go to constants\charmap.asm and assign "names" to those star tiles. You'll replace the entries for the Japanese characters you drew over:
...
charmap "!", $e7
charmap ".", $e8
- charmap "ァ", $e9 ; katakana small a, unused
- charmap "ゥ", $ea ; katakana small u, unused
- charmap "ェ", $eb ; katakana small e, unused
+ charmap "<*>", $e9 ; shiny star, 1 on left tile
+ charmap "<**>", $ea ; shiny stars, 2 on right tile
+ charmap "<***>", $eb ; shiny stars, 3 on single tile
charmap "▷", $ec
charmap "▶", $ed
...Don't neglect the "<>" symbols around the asterisks.
Once this is all in place, you'll apply these new graphics to their intended locations.
Go to engine\pokemon\status_screen.asm. Find the lengthy StatusScreen: routine and locate the .StatusWritten section, where you'll insert the following:
...
predef IndexToPokedex
hlcoord 3, 7
ld de, wPokedexNum
lb bc, LEADING_ZEROES | 1, 3
call PrintNumber ; Pokémon no.
+; check whether to print stars for shiny Mon
+ ld a, [wShinyMon]
+ and a
+ jr z, .PrintType
+ hlcoord 6, 7
+ ld de, ShinyStarsText
+ call PlaceString
+.PrintType
hlcoord 11, 10
predef PrintMonType
...Now define ShinyStarsText: just a bit further down in the same file:
...
OKText:
db "OK@"
+ShinyStarsText:
+ db "<*><**>@"
+
; Draws a line starting from hl high b and wide c
DrawLineBox:
...This will cause stars to display next to the shiny Pokemon's Dex number on any status screen, whether the screen is accessed from the START menu, the PC, or during battle, as seen below:
Go to engine\battle\core.asm. We'll start by adding stars to the enemy HUD. Find DrawEnemyHUDAndHPBar: and simply insert the following:
...
call ClearScreenArea
callfar PlaceEnemyHUDTiles
+ ld a, [wShinyMon]
+ and a
+ jr z, .getEnemyMonName
+ hlcoord 8, 1
+ ld de, ShinyStarsBattleText
+ call PlaceString
+.getEnemyMonName
ld de, wEnemyMonNick
hlcoord 1, 0
call CenterMonName
...Now we need to draw stars on our own shiny Pokemon's battle HUD. In the same file, go to DrawPlayerHUDAndHPBar: and make a similar insertion to the previous one:
...
call CenterMonName
call PlaceString
+; check to see if we need to print shiny stars on the player HUD
+ ld a, [wShinyBattleMon]
+ and a
+ jr z, .getBattleMonData
+ hlcoord 18, 8
+ ld de, ShinyStarsBattleText
+ call PlaceString
+.getBattleMonData
ld hl, wBattleMonSpecies
...But wait! We need to make a change to the order in which HUD elements are loaded for the player's side; as things stand, the appearance/disappearance of the player HUD shiny stars will be delayed (in many cases significantly) when Pokemon are swapped in and out of battle. To solve this, find the SendOutMon: routine and move the SET_PAL_BATTLE instruction:
SendOutMon:
callfar PrintSendOutMonMessage
ld hl, wEnemyMonHP
ld a, [hli]
or [hl] ; is enemy mon HP zero?
jp z, .skipDrawingEnemyHUDAndHPBar ; if HP is zero, skip drawing the HUD and HP bar
call DrawEnemyHUDAndHPBar
.skipDrawingEnemyHUDAndHPBar
+ ld b, SET_PAL_BATTLE
+ call RunPaletteCommand
call DrawPlayerHUDAndHPBar
predef LoadMonBackPic
xor a
ldh [hStartTileID], a
ld hl, wBattleAndStartSavedMenuItem
ld [hli], a
ld [hl], a
ld [wBoostExpByExpAll], a
ld [wDamageMultipliers], a
ld [wPlayerMoveNum], a
ld hl, wPlayerUsedMove
ld [hli], a
ld [hl], a
ld hl, wPlayerStatsToDouble
ld [hli], a
ld [hli], a
ld [hli], a
ld [hli], a
ld [hl], a
ld [wPlayerDisabledMove], a
ld [wPlayerDisabledMoveNumber], a
ld [wPlayerMonMinimized], a
- ld b, SET_PAL_BATTLE
- call RunPaletteCommand
ld hl, wEnemyBattleStatus1
res USING_TRAPPING_MOVE, [hl]
...Moving SET_PAL_BATTLE forces the game to update the value in wShinyBattleMon earlier so the player's battle HUD shiny stars will in turn update in a timely manner. This comes with the VERY negligible side effect of delaying the update of the player battle HUD as a whole when a Pokemon is sent out. The delay is literally only a few frames, but it's JUST enough to be noticeable if playing at regular speed (and if you've been playing this game for nearly 30 years and are accustomed to the EXACT timing of HUD updates...).
Now define ShinyStarsBattleText. Scroll down to the very end of the file and add this:
...
ldh a, [hLoadedROMBank]
ld b, a
jp CopyVideoData
+ShinyStarsBattleText:
+ db "<***>@"
+The battle HUD will now look like this when shiny Pokemon are in battle:
We're pretty much done... finally. All that's left is to enable shiny-sprite-toggling in the Pokedex, and we'll be finished!
Open engine\menus\pokedex.asm. Find ShowPokedexDataInternal: and add this to the beginning of the routine:
; function to display pokedex data from inside the pokedex
ShowPokedexDataInternal:
+ xor a
+ ld [wShinyMon], a ; ensure the Pokedex shows standard palettes by default
ld hl, wStatusFlags2
set BIT_NO_AUDIO_FADE_OUT, [hl]
...Now, in the same routine, scroll way down to .waitForButtonPress:
...
.waitForButtonPress
call JoypadLowSensitivity
+ ldh a, [hJoyPressed]
+ bit B_PAD_SELECT, a
+ jr z, .checkIfAOrBPressed
+; SELECT was pressed
+ ld a, [wShinyMon]
+ and a ; are shiny palettes already active?
+ jr nz, .resetDexPalette ; if so, jump to reset standard palettes
+ inc a ; 'a' now equals 1 (the shiny palette value)
+ jr .swapDexPalette
+.resetDexPalette
+ xor a ; 'a' now equals 0 (the standard palette value)
+.swapDexPalette
+ ld [wShinyMon], a ; load new value into wShinyMon
+ ld b, SET_PAL_POKEDEX
+ call RunPaletteCommand ; change on-screen Mon palette
+ jr .waitForButtonPress
+.checkIfAOrBPressed
ldh a, [hJoy5]
and PAD_A | PAD_B
jr z, .waitForButtonPress
...Now pressing the SELECT button will toggle shininess for the currently-viewed Pokemon. The only small hitch is that the toggle will only work after you've scrolled to the end of the Pokedex text, but that's a minor issue.
And that's it! I think I've managed to cram in all the shiny-related stuff I have in my own ROMhack. Try it out for yourself!