-
Notifications
You must be signed in to change notification settings - Fork 806
Color party menu icons by species
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.)
- Define the eight icon palettes
- Assign the palettes to Pokémon
- Allow sprite animations to have dynamic palette attributes
- Make party icons' sprite animation data use dynamic palettes
- Load the correct icon palettes for species and shininess
- Fix a bank overflow error
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.)
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.
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.
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.
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. ;)
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 INCLUDE
s 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!
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.