Skip to content

Color party menu icons by species

Idain edited this page Jun 17, 2022 · 29 revisions

The tutorial to 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
  2. Assign the palettes to Pokémon
  3. Allow sprite animations to have dynamic palette attributes
  4. Make party icons' sprite animation data use dynamic palettes
  5. Load the correct icon palettes for species and shininess
  6. Fix a bank overflow error

1. Define the eight icon palettes

Edit constants/icon_constants.asm, adding this to the end:

+; 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; delete the entire content and replace it with this:

+	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:

 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, and/or the tutorial to add a third player choice, 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:

+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:

 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:

 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:

 ; 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:

 .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:

 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 again:

 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, [wTempIconSpecies]
+	ld [wCurPartySpecies], a
+	call GetMenuMonIconPalette
+	ld hl, wVirtualOAMSprite00Attributes
+	jr _ApplyMenuMonIconColor
+
+SetMenuMonIconColor_NoShiny:
+	push hl
+	push de
+	push bc
+	push af
+
+	ld a, [wTempIconSpecies]
+	ld [wCurPartySpecies], a
+	and a
+	call GetMenuMonIconPalette_PredeterminedShininess
+	ld hl, wVirtualOAMSprite00Attributes
+	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
+	ret
 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
 	...
 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:

 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 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 INCLUDEs 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:

+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

This is totally compatible with the tutorial to add a unique icon for each Pokémon; in that tutorial, we just split gfx/icons.asm into more than one section, and change how the graphics' bank is determined.

Clone this wiki locally