[The tutorial to add a new party menu icon](Add-a-new-party-menu-icon) explains how to add a different icon for each Pokémon species, but the icons are all still red. This tutorial will explain how to color each species' icon from a set of eight palettes. (Coloring them uniquely, with the same palettes as their battle sprites, is beyond the scope of this tutorial.) ## Contents 1. [Define the eight icon palettes](#1-define-the-eight-icon-palettes) 2. [Assign the palettes to Pokémon](#2-assign-the-palettes-to-pokémon) 3. [Allow sprite animations to have dynamic palette attributes](#3-allow-sprite-animations-to-have-dynamic-palette-attributes) 4. [Make party icons' sprite animation data use dynamic palettes](#4-make-party-icons-sprite-animation-data-use-dynamic-palettes) 5. [Load the correct icon palettes for species and shininess](#5-load-the-correct-icon-palettes-for-species-and-shininess) 6. [Fix a bank overflow error](#6-fix-a-bank-overflow-error) ## 1. Define the eight icon palettes Edit [constants/icon_constants.asm](../blob/master/constants/icon_constants.asm), adding this to the end: ```diff +; party menu icon palettes + const_def + const PAL_ICON_RED ; 0 + const PAL_ICON_BLUE ; 1 + const PAL_ICON_GREEN ; 2 + const PAL_ICON_BROWN ; 3 + const PAL_ICON_PINK ; 4 + const PAL_ICON_GRAY ; 5 + const PAL_ICON_TEAL ; 6 + const PAL_ICON_PURPLE ; 7 ``` Then edit [gfx/stats/party_menu_ob.pal](../blob/master/gfx/stats/party_menu_ob.pal); delete the entire content and replace it with this: ```diff + RGB 27,31,27, 31,19,10, 31,07,01, 00,00,00 ; red + RGB 27,31,27, 31,19,10, 10,09,31, 00,00,00 ; blue + RGB 27,31,27, 31,19,10, 07,23,03, 00,00,00 ; green + RGB 27,31,27, 31,19,10, 15,10,03, 00,00,00 ; brown + RGB 27,31,27, 31,19,10, 30,10,06, 00,00,00 ; pink + RGB 27,31,27, 31,19,10, 13,13,13, 00,00,00 ; gray + RGB 27,31,27, 31,19,10, 03,23,21, 00,00,00 ; teal + RGB 27,31,27, 31,19,10, 18,04,18, 00,00,00 ; purple ``` Finally, edit [engine/gfx/color.asm](../blob/master/engine/gfx/color.asm): ```diff InitPartyMenuOBPals: ld hl, PartyMenuOBPals ld de, wOBPals1 - ld bc, 2 palettes + ld bc, 8 palettes ld a, BANK(wOBPals1) call FarCopyWRAM ret ``` These will be the eight possible colors for all 251 Pokémon (and Eggs). You can choose any colors you want, but the first two will have to be red and blue respectively, since the Fly map uses those for the player sprite at the same time as it shows the Flying Pokémon. (`InitPartyMenuOBPals` used to *only* load those first two, since they're all that was needed: Chris and the Pokémon were red, Kris was blue.) (If you've followed [the tutorial to edit the player colors](Edit-the-male-and-female-player-colors), and/or [the tutorial to add a third player choice](Add-a-new-player-gender), that's fine; just make sure that the possible player colors all correspond to identical Pokémon icon colors.) ## 2. Assign the palettes to Pokémon Create **data/pokemon/menu_icon_pals.asm**: ```diff +icon_pals: MACRO + dn PAL_ICON_\1, PAL_ICON_\2 +ENDM + +MonMenuIconPals: + table_width 1, MonMenuIconPals + ; normal, shiny + icon_pals TEAL, GREEN ; BULBASAUR + icon_pals TEAL, GREEN ; IVYSAUR + icon_pals TEAL, GREEN ; VENUSAUR + icon_pals RED, BROWN ; CHARMANDER + icon_pals RED, BROWN ; CHARMELEON + icon_pals RED, PURPLE ; CHARIZARD + icon_pals BLUE, TEAL ; SQUIRTLE + icon_pals BLUE, TEAL ; WARTORTLE + icon_pals BLUE, TEAL ; BLASTOISE + ... + icon_pals GREEN, TEAL ; LARVITAR + icon_pals BLUE, PURPLE ; PUPITAR + icon_pals GREEN, BROWN ; TYRANITAR + icon_pals BLUE, TEAL ; LUGIA + icon_pals RED, BROWN ; HO_OH + icon_pals GREEN, PINK ; CELEBI + assert_table_length NUM_POKEMON + + icon_pals RED, RED ; 252 + icon_pals RED, BLUE ; EGG + icon_pals RED, RED ; 254 ``` Then edit [engine/gfx/mon_icons.asm](../blob/master/engine/gfx/mon_icons.asm): ```diff INCLUDE "data/pokemon/menu_icons.asm" + +INCLUDE "data/pokemon/menu_icon_pals.asm" INCLUDE "data/icon_pointers.asm" INCLUDE "gfx/icons.asm" ``` This defines the normal and shiny color for each Pokémon. Note that `EGG` (index 253, or $FD) also has an entry; if you give it a different shiny color (here `BLUE` instead of `RED`) you'll be able to tell shiny Eggs apart. ## 3. Allow sprite animations to have dynamic palette attributes The sprite animation engine has certain data for Pokémon icons, Pokémon holding items, and Pokémon holding Mail. This data is accessed at certain points in the code, like the party menu and the Fly map, and is hard-coded to use `PAL_OW_RED`. To change the color based on species, we need to achieve two sub-goals: get the right color corresponding to the current Pokémon species and whether it's shiny; apply that color to the sprite animation; and don't then force the animation to be red. Let's take those in reverse order; it's easier. Edit [engine/gfx/sprites.asm](../blob/master/engine/gfx/sprites.asm): ```diff UpdateAnimFrame: ... ; fourth byte: attributes ; [de] = GetSpriteOAMAttr([hl]) + ld a, [hl] + cp -1 ; use whatever attributes were already loaded (for party icons) + jr z, .skip_attributes call GetSpriteOAMAttr ld [de], a +.skip_attributes inc hl inc de ld a, e ld [wCurSpriteOAMAddr], a cp LOW(wVirtualOAMEnd) jr nc, .reached_the_end dec c jr nz, .loop pop bc jr .done ``` Now we'll be able to load a species-appropriate palette index, and then load some sprite animation data; if that data uses −1 for the attribute value, it will just apply the already-loaded palette index instead. We'll see where that −1 value gets used in the next step. ## 4. Make party icons' sprite animation data use dynamic palettes First, edit [constants/sprite_anim_constants.asm](../blob/master/constants/sprite_anim_constants.asm): ```diff ; SpriteAnimOAMData indexes (see data/sprite_anims/oam.asm) const_def const SPRITE_ANIM_OAMSET_RED_WALK_1 ; 00 const SPRITE_ANIM_OAMSET_RED_WALK_2 ; 01 ... const SPRITE_ANIM_OAMSET_PARTY_MON_WITH_MAIL_1 ; 3d const SPRITE_ANIM_OAMSET_PARTY_MON_WITH_MAIL_2 ; 3e const SPRITE_ANIM_OAMSET_PARTY_MON_WITH_ITEM_1 ; 3f const SPRITE_ANIM_OAMSET_PARTY_MON_WITH_ITEM_2 ; 40 ... const SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_10 ; 8a const SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_11 ; 8b + const SPRITE_ANIM_OAMSET_PARTY_MON_1 ; 8c + const SPRITE_ANIM_OAMSET_PARTY_MON_2 ; 8d ``` We're going to be updating the data soon for those `SPRITE_ANIM_OAMSET_PARTY_MON_WITH_MAIL_*` and `SPRITE_ANIM_OAMSET_PARTY_MON_WITH_ITEM_*` indexes; but the `SPRITE_ANIM_OAMSET_RED_WALK_*` indexes are used for more things than just Pokémon, so we're creating new `SPRITE_ANIM_OAMSET_PARTY_MON_*` indexes for plain party icons. Edit [data/sprite_anims/framesets.asm](../blob/master/data/sprite_anims/framesets.asm): ```diff .Frameset_PartyMon: - frame SPRITE_ANIM_OAMSET_RED_WALK_1, 8 - frame SPRITE_ANIM_OAMSET_RED_WALK_2, 8 + frame SPRITE_ANIM_OAMSET_PARTY_MON_1, 8 + frame SPRITE_ANIM_OAMSET_PARTY_MON_2, 8 dorestart .Frameset_PartyMonWithMail: frame SPRITE_ANIM_OAMSET_PARTY_MON_WITH_MAIL_1, 8 frame SPRITE_ANIM_OAMSET_PARTY_MON_WITH_MAIL_2, 8 dorestart .Frameset_PartyMonWithItem: frame SPRITE_ANIM_OAMSET_PARTY_MON_WITH_ITEM_1, 8 frame SPRITE_ANIM_OAMSET_PARTY_MON_WITH_ITEM_2, 8 dorestart .Frameset_PartyMonFast: - frame SPRITE_ANIM_OAMSET_RED_WALK_1, 4 - frame SPRITE_ANIM_OAMSET_RED_WALK_2, 4 + frame SPRITE_ANIM_OAMSET_PARTY_MON_1, 4 + frame SPRITE_ANIM_OAMSET_PARTY_MON_2, 4 dorestart .Frameset_PartyMonWithMailFast: frame SPRITE_ANIM_OAMSET_PARTY_MON_WITH_MAIL_1, 4 frame SPRITE_ANIM_OAMSET_PARTY_MON_WITH_MAIL_2, 4 dorestart .Frameset_PartyMonWithItemFast: frame SPRITE_ANIM_OAMSET_PARTY_MON_WITH_ITEM_1, 4 frame SPRITE_ANIM_OAMSET_PARTY_MON_WITH_ITEM_2, 4 dorestart .Frameset_RedWalk: frame SPRITE_ANIM_OAMSET_RED_WALK_1, 8 frame SPRITE_ANIM_OAMSET_RED_WALK_2, 8 frame SPRITE_ANIM_OAMSET_RED_WALK_1, 8 frame SPRITE_ANIM_OAMSET_RED_WALK_2, 8, OAM_X_FLIP dorestart ``` Edit [data/sprite_anims/oam.asm](../blob/master/data/sprite_anims/oam.asm): ```diff SpriteAnimOAMData: ; entries correspond to SPRITE_ANIM_OAMSET_* constants table_width 3, SpriteAnimOAMData ; vtile offset, data pointer dbw $00, .OAMData_RedWalk ; SPRITE_ANIM_OAMSET_RED_WALK_1 dbw $04, .OAMData_RedWalk ; SPRITE_ANIM_OAMSET_RED_WALK_2 ... dbw $00, .OAMData_PartyMonWithMail1 ; SPRITE_ANIM_OAMSET_PARTY_MON_WITH_MAIL_1 dbw $00, .OAMData_PartyMonWithMail2 ; SPRITE_ANIM_OAMSET_PARTY_MON_WITH_MAIL_2 dbw $00, .OAMData_PartyMonWithItem1 ; SPRITE_ANIM_OAMSET_PARTY_MON_WITH_ITEM_1 dbw $00, .OAMData_PartyMonWithItem2 ; SPRITE_ANIM_OAMSET_PARTY_MON_WITH_ITEM_2 ... dbw $04, .OAMData_GameFreakLogo4_11 ; SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_10 dbw $00, .OAMData_GameFreakLogo4_11 ; SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_11 + dbw $00, .OAMData_PartyMon ; SPRITE_ANIM_OAMSET_PARTY_MON_1 + dbw $04, .OAMData_PartyMon ; SPRITE_ANIM_OAMSET_PARTY_MON_2 assert_table_length NUM_SPRITE_ANIM_OAMSETS ... .OAMData_PartyMonWithMail1: db 4 - dbsprite -1, -1, 0, 0, $00, PAL_OW_RED - dbsprite 0, -1, 0, 0, $01, PAL_OW_RED - dbsprite -1, 0, 0, 0, $08, PAL_OW_RED - dbsprite 0, 0, 0, 0, $03, PAL_OW_RED + dbsprite -1, -1, 0, 0, $00, -1 + dbsprite 0, -1, 0, 0, $01, -1 + dbsprite -1, 0, 0, 0, $08, PAL_ICON_RED + dbsprite 0, 0, 0, 0, $03, -1 .OAMData_PartyMonWithMail2: db 4 - dbsprite -1, -1, 0, 0, $04, PAL_OW_RED - dbsprite 0, -1, 0, 0, $05, PAL_OW_RED - dbsprite -1, 0, 0, 0, $08, PAL_OW_RED - dbsprite 0, 0, 0, 0, $07, PAL_OW_RED + dbsprite -1, -1, 0, 0, $04, -1 + dbsprite 0, -1, 0, 0, $05, -1 + dbsprite -1, 0, 0, 0, $08, PAL_ICON_RED + dbsprite 0, 0, 0, 0, $07, -1 .OAMData_PartyMonWithItem1: db 4 - dbsprite -1, -1, 0, 0, $00, PAL_OW_RED - dbsprite 0, -1, 0, 0, $01, PAL_OW_RED - dbsprite -1, 0, 0, 0, $09, PAL_OW_RED - dbsprite 0, 0, 0, 0, $03, PAL_OW_RED + dbsprite -1, -1, 0, 0, $00, -1 + dbsprite 0, -1, 0, 0, $01, -1 + dbsprite -1, 0, 0, 0, $09, PAL_ICON_RED + dbsprite 0, 0, 0, 0, $03, -1 .OAMData_PartyMonWithItem2: db 4 - dbsprite -1, -1, 0, 0, $04, PAL_OW_RED - dbsprite 0, -1, 0, 0, $05, PAL_OW_RED - dbsprite -1, 0, 0, 0, $09, PAL_OW_RED - dbsprite 0, 0, 0, 0, $07, PAL_OW_RED + dbsprite -1, -1, 0, 0, $04, -1 + dbsprite 0, -1, 0, 0, $05, -1 + dbsprite -1, 0, 0, 0, $09, PAL_ICON_RED + dbsprite 0, 0, 0, 0, $07, -1 + +.OAMData_PartyMon: + db 4 + dbsprite -1, -1, 0, 0, $00, -1 + dbsprite 0, -1, 0, 0, $01, -1 + dbsprite -1, 0, 0, 0, $02, -1 + dbsprite 0, 0, 0, 0, $03, -1 ``` Now Pokémon icon sprites will use whatever palette index was already loaded, except for their held item or Mail graphics, which will use `PAL_ICON_RED`. Of course, items and Mail could use differents colors instead. ## 5. Load the correct icon palettes for species and shininess Edit [engine/gfx/mon_icons.asm](../blob/master/engine/gfx/mon_icons.asm) again: ```diff LoadOverworldMonIcon: ld a, e call ReadMonMenuIcon ld l, a ld h, 0 add hl, hl ld de, IconPointers add hl, de ld a, [hli] ld e, a ld d, [hl] ld b, BANK(Icons) ld c, 8 ret + +SetMenuMonIconColor: + push hl + push de + push bc + push af + + ld a, [wd265] + ld [wCurPartySpecies], a + call GetMenuMonIconPalette + jr _ApplyMenuMonIconColor + +SetMenuMonIconColor_NoShiny: + push hl + push de + push bc + push af + + ld a, [wd265] + ld [wCurPartySpecies], a + and a + call GetMenuMonIconPalette_PredeterminedShininess + jr _ApplyMenuMonIconColor + +LoadPartyMenuMonIconColors: + push hl + push de + push bc + push af + + ld a, [wPartyCount] + sub c + ld [wCurPartyMon], a + ld e, a + ld d, 0 + + ld hl, wPartyMon1Item + call GetPartyLocation + ld a, [hl] + ld [wCurIconMonHasItemOrMail], a + + ld hl, wPartySpecies + add hl, de + ld a, [hl] + ld [wCurPartySpecies], a + ld a, MON_DVS + call GetPartyParamLocation + call GetMenuMonIconPalette + ld hl, wVirtualOAMSprite00Attributes + push af + ld a, [wCurPartyMon] + swap a + ld d, 0 + ld e, a + add hl, de + pop af + + ld de, 4 + ld [hl], a ; top left + add hl, de + ld [hl], a ; top right + add hl, de + push hl + add hl, de + ld [hl], a ; bottom right + pop hl + ld d, a + ld a, [wCurIconMonHasItemOrMail] + and a + ld a, PAL_OW_RED ; item or mail color + jr nz, .ok + ld a, d +.ok + ld [hl], a ; bottom left + jr _FinishMenuMonIconColor + +_ApplyMenuMonIconColor: + ld c, 4 + ld de, 4 +.loop + ld [hl], a + add hl, de + dec c + jr nz, .loop + ; fallthrough +_FinishMenuMonIconColor: + pop af + pop bc + pop de + pop hl + ret + +GetMenuMonIconPalette: + ld c, l + ld b, h + farcall CheckShininess +GetMenuMonIconPalette_PredeterminedShininess: + push af + ld a, [wCurPartySpecies] + dec a + ld c, a + ld b, 0 + ld hl, MonMenuIconPals + add hl, bc + ld e, [hl] + pop af + ld a, e + jr c, .shiny + swap a +.shiny + and $f + ld l, a + ret ``` ```diff InitPartyMenuIcon: + call LoadPartyMenuMonIconColors ld a, [wCurIconTile] push af ldh a, [hObjectStructIndex] ld hl, wPartySpecies ld e, a ld d, 0 add hl, de ld a, [hl] call ReadMonMenuIcon ld [wCurIcon], a call GetMemIconGFX ... ``` ```diff NamingScreen_InitAnimatedMonIcon: + ld hl, wTempMonDVs + call SetMenuMonIconColor ld a, [wTempIconSpecies] call ReadMonMenuIcon ld [wCurIcon], a xor a call GetIconGFX depixel 4, 4, 4, 0 ld a, SPRITE_ANIM_INDEX_PARTY_MON call _InitSpriteAnimStruct ld hl, SPRITEANIMSTRUCT_ANIM_SEQ_ID add hl, bc ld [hl], SPRITE_ANIM_SEQ_NULL ret MoveList_InitAnimatedMonIcon: + ld a, MON_DVS + call GetPartyParamLocation + call SetMenuMonIconColor ld a, [wTempIconSpecies] call ReadMonMenuIcon ld [wCurIcon], a xor a call GetIconGFX ld d, 3 * 8 + 2 ; depixel 3, 4, 2, 4 ld e, 4 * 8 + 4 ld a, SPRITE_ANIM_INDEX_PARTY_MON call _InitSpriteAnimStruct ld hl, SPRITEANIMSTRUCT_ANIM_SEQ_ID add hl, bc ld [hl], SPRITE_ANIM_SEQ_NULL ret Trade_LoadMonIconGFX: ld a, [wTempIconSpecies] call ReadMonMenuIcon ld [wCurIcon], a ld a, $62 ld [wCurIconTile], a call GetMemIconGFX ret GetSpeciesIcon: ; Load species icon into VRAM at tile a push de + ld a, MON_DVS + call GetPartyParamLocation + call SetMenuMonIconColor ld a, [wTempIconSpecies] call ReadMonMenuIcon ld [wCurIcon], a pop de ld a, e call GetIconGFX ret FlyFunction_GetMonIcon: push de ld a, [wTempIconSpecies] call ReadMonMenuIcon ld [wCurIcon], a pop de ld a, e call GetIcon_a ret ``` And edit [wram.asm](../blob/master/wram.asm): ```diff wHandlePlayerStep:: db - ds 1 +wCurIconMonHasItemOrMail:: db wPartyMenuActionText:: db ``` That code will load the correct icon colors on certain screens: the party menu, nickname screen, move list, and Fly map. Notably, the trade animation and overworld Fly animation do *not* load the new palettes. That's because those screens use some of the sprite palettes for their own elements (the trade tube, and other overworld sprites), so not all the possible species would have their correct colors available. There are different possible ways to overcome this, such as redesigning the trade and Fly animations, or converting some icon palettes on those screens, but that's left as an exercise for the reader. ;) ## 6. Fix a bank overflow error We're done implementing the new palettes, but `make` won't build the ROM: ``` ERROR: main.asm(324) -> engine/gfx/mon_icons.asm(556) -> gfx/icons.asm(40): Section 'bank23' is too big (max size = 0x4000 bytes, reached 0x403F). ``` All that code and data we added has overflowed section "bank23", which [layout.link](../blob/master/layout.link) tells us is in ROM bank $23. (Okay, duh, but not every section is named after its bank.) We need to move some content to a different bank. It turns out that [engine/gfx/mon_icons.asm](../blob/master/engine/gfx/mon_icons.asm) `INCLUDE`s [gfx/icons.asm](../blob/master/gfx/icons.asm), which is just all the icon graphics. They're loaded by bank anyway, so they can freely be placed in any bank without causing errors. Edit [gfx/icons.asm](../blob/master/gfx/icons.asm): ```diff +SECTION "Mon Icons", ROMX + Icons: ; used only for BANK(Icons) NullIcon: PoliwagIcon: INCBIN "gfx/icons/poliwag.2bpp" JigglypuffIcon: INCBIN "gfx/icons/jigglypuff.2bpp" DiglettIcon: INCBIN "gfx/icons/diglett.2bpp" ... ``` *Now* it builds correctly! ![Screenshot](screenshots/party-menu-icon-colors.png) This is totally compatible with [the tutorial to add a unique icon for each Pokémon](Add-a-new-party-menu-icon); in that tutorial, we just split [gfx/icons.asm](../blob/master/gfx/icons.asm) into more than one section, and change how the graphics' bank is determined.