-
Notifications
You must be signed in to change notification settings - Fork 989
Add All Unique Party Menu Sprite Icons
The original party mon sprite system is pretty simple and limited. When the party menu is opened, all possible sprites are loaded into VRAM (since there aren't very many), and then the appropriate tile IDs are intelligently placed into OAM based on the mon present in the party.
In order to handle all 151 possible sprites (or more!), we're going to take an opposite approach: intelligently load the correct tiles into VRAM based on the mon present in the party, then place some simple, pre-determined data into OAM.
For starters, we need a few routines to load our custom sprites and custom OAM data:
- LoadSinglePartyMonSprite: This will be used in special cases to load a single sprite (the Nickname screen and Trade animation)
- LoadPartyMonSprites: This is the main routine to load the whole party into VRAM
- LoadPartyMonSprite: Some common code used by both of the above routines
- PlacePartyMonSprite: This routine copies some of our pre-determined data to OAM
Additionally, we need some data:
- PartyMonOAM: The pre-determined OAM data for the sprites
- PartyMonSprites: The new sprites
Add this code to the very bottom of engine/gfx/mon_icons.asm. The code is pretty straightforward and pretty well commented. Take a moment to read through it to get an idea of how it works.
PUSHS
SECTION "Party Mon Sprites Routines", ROMX
; load and place the party mon icon according to wMonPartySpriteSpecies
LoadSinglePartyMonSprite:
; load into the start of VRAM
call DisableLCD
ld a, [wMonPartySpriteSpecies]
ld de, vSprites
call LoadPartyMonSprite
call EnableLCD
; place into the start of OAM
ld a, [hPartyMonIndex]
push af
xor a
ld [hPartyMonIndex], a
call PlacePartyMonSprite
pop af
ld [hPartyMonIndex], a
ret
; load the party mon icon for all mon in the party
LoadPartyMonSprites:
call DisableLCD
ld de, vSprites
ld hl, wPartySpecies
.loop
ld a, [hli]
cp -1
jr z, .done
push hl
call LoadPartyMonSprite
pop hl
jr .loop
.done
jp EnableLCD
; copy the 8-tile icon for the mon in register a to de
LoadPartyMonSprite:
push de
ld [wd11e], a
predef IndexToPokedex
; hMultiplicand = pokedex number - 1
xor a
ld [hMultiplicand], a
ld [hMultiplicand + 1], a
ld a, [wd11e]
dec a
ld [hMultiplicand + 2], a
; hMultiplier = icon size, in bytes
ld a, 8 tiles
ld [hMultiplier], a
call Multiply
; hl = icon offset
ld a, [hProduct + 2]
ld h, a
ld a, [hProduct + 3]
ld l, a
; if offset < $4000, use first icon bank
bit 6, h
set 6, h
ld a, BANK(PartyMonSprites)
jr z, .gotBank
; otherwise, use second icon bank
inc a
.gotBank
ld bc, 8 tiles
pop de
jp FarCopyData
; copy 1 full entry (16 bytes) from PartyMonOAM into wShadowOAM according to hPartyMonIndex
; and backup wShadowOAM into wMonPartySpritesSavedOAM
PlacePartyMonSprite:
push hl
push de
push bc
; bc = hPartyMonIndex * 16
ld a, [hPartyMonIndex]
add a
add a
add a
add a
ld c, a
ld b, 0
; de = destination address
ld hl, wShadowOAM
add hl, bc
ld d, h
ld e, l
; hl = source address
ld hl, PartyMonOAM
add hl, bc
ld bc, 4 * 4
call CopyData
; make backup
ld hl, wShadowOAM
ld de, wMonPartySpritesSavedOAM
ld bc, 4 * 4 * PARTY_LENGTH
call CopyData
pop bc
pop de
pop hl
ret
PartyMonOAM:
db $10, $10, $00, $00
db $10, $18, $01, $00
db $18, $10, $04, $00
db $18, $18, $05, $00
db $20, $10, $08, $00
db $20, $18, $09, $00
db $28, $10, $0c, $00
db $28, $18, $0d, $00
db $30, $10, $10, $00
db $30, $18, $11, $00
db $38, $10, $14, $00
db $38, $18, $15, $00
db $40, $10, $18, $00
db $40, $18, $19, $00
db $48, $10, $1c, $00
db $48, $18, $1d, $00
db $50, $10, $20, $00
db $50, $18, $21, $00
db $58, $10, $24, $00
db $58, $18, $25, $00
db $60, $10, $28, $00
db $60, $18, $29, $00
db $68, $10, $2c, $00
db $68, $18, $2d, $00
SECTION "Party Mon Sprites Gfx 1", ROMX
PartyMonSprites:
INCBIN "gfx/icons/party_mon_sprites.2bpp", $0, $4000
SECTION "Party Mon Sprites Gfx 2", ROMX
INCBIN "gfx/icons/party_mon_sprites.2bpp", $4000
POPS
For simplicity, all sprites are kept in a single PNG file. This has some pros and cons. You're free to split this into 151 separate PNGs if you wish.
Save this PNG as gfx/icons/party_mon_sprites.png:
party_mon_sprites.png
These sprites were created by Blue Emerald, Chamber_, Soloo993, et al.
Note that this file cannot fit into a single ROM bank. Therefore, it is split into 2 sections: "Party Mon Sprites Gfx 1" and "Party Mon Sprites Gfx 2"
Next, add these new sections to layout.link:
"Pokédex Text"
ROMX $2C
"Move Names"
+ROMX $30
+ org $4000
+ "Party Mon Sprites Gfx 1"
+ROMX $31
+ org $4000
+ "Party Mon Sprites Gfx 2"
+ "Party Mon Sprites Routines"
WRAM0
"Audio RAM"
org $c100
Note that "Party Mon Sprites Gfx 2" must be in the bank immediately after "Party Mon Sprites Gfx 1", and they must both be at $4000 in their respective bank.
Another quirk of the original party sprite system is that not all sprites have 2 frames of animation. ICON_BALL
and ICON_HELIX
only have 1 frame of animation and simply bounce up and down instead.
Now, all mon have 2 frames of animation, so we need to remove the code for the special handling of these cases. This is in AnimatePartyMon
in engine/gfx/mon_icons.asm:
ld bc, $10
ld a, [wCurrentMenuItem]
call AddNTimes
- ld c, ICONOFFSET
- ld a, [hl]
- cp ICON_BALL << 2
- jr z, .editCoords
- cp ICON_HELIX << 2
- jr nz, .editTileIDS
-; ICON_BALL and ICON_HELIX only shake up and down
-.editCoords
- dec hl
- dec hl ; dec hl to the OAM y coord
- ld c, $1 ; amount to increase the y coord by
-; otherwise, load a second sprite frame
-.editTileIDS
+ ld c, $2
ld b, $4
ld de, $4
.loop
Now we will always use a tile offset of 2 between the two frames of animation for every sprite.
Optional: While you're here, you may want to update the animation speeds to match Gen 2, which look a little better with the new sprites:
; that each frame lasts for green HP, yellow HP, and red HP in order.
; On the naming screen, the yellow HP speed is always used.
PartyMonSpeeds:
- db 5, 16, 32
+ db 10, 24, 32
Next, we'll actually hook up these routines into the party menu.
In engine/menus/party_menu.asm, replace LoadMonPartySpriteGfxWithLCDDisabled
with our new LoadPartyMonSprites
(and also add a new label that we will use in the next step):
ldh [hAutoBGTransferEnabled], a
call ClearScreen
call UpdateSprites
- farcall LoadMonPartySpriteGfxWithLCDDisabled ; load pokemon icon graphics
+RedrawPartyMenu_ReloadSprites:
+ farcall LoadPartyMonSprites ; load pokemon icon graphics
RedrawPartyMenu_::
ld a, [wPartyMenuTypeOrMessageID]
and replace WriteMonPartySpriteOAMByPartyIndex
with our new PlacePartyMonSprite
:
call GetPartyMonName
pop hl
call PlaceString ; print the pokemon's name
- farcall WriteMonPartySpriteOAMByPartyIndex ; place the appropriate pokemon icon
+ farcall PlacePartyMonSprite ; place the appropriate pokemon icon
ldh a, [hPartyMonIndex]
ld [wWhichPokemon], a
inc a
Next, in engine/menus/start_sub_menus.asm we'll use the new label that we just created in the previous step, at the bottom of SwitchPartyMon
:
call SwitchPartyMon_ClearGfx
ld a, [wCurrentMenuItem]
call SwitchPartyMon_ClearGfx
- jp RedrawPartyMenu_
+ jp RedrawPartyMenu_ReloadSprites
This is so that VRAM tiles are properly reloaded after a swap in the party menu.
Next, we will fix the Naming screen.
In engine/menus/naming_screen.asm, remove the call to LoadMonPartySpriteGfx
from the DisplayNamingScreen
routine since we don't need the old icons anymore:
call RunPaletteCommand
call LoadHpBarAndStatusTilePatterns
call LoadEDTile
- farcall LoadMonPartySpriteGfx
hlcoord 0, 4
ld b, 9
ld c, 18
and call our new LoadSinglePartyMonSprite
instead of the old WriteMonPartySpriteOAMBySpecies
in PrintNamingText
:
ld a, [wcf91]
ld [wMonPartySpriteSpecies], a
push af
- farcall WriteMonPartySpriteOAMBySpecies
+ farcall LoadSinglePartyMonSprite
pop af
ld [wd11e], a
call GetMonName
Lastly, we will fix the Trade animation screen by replacing the old WriteMonPartySpriteOAMBySpecies
with the new LoadSinglePartyMonSprite
in engine/movie/trade.asm:
Trade_WriteCircledMonOAM:
- farcall WriteMonPartySpriteOAMBySpecies
+ farcall LoadSinglePartyMonSprite
call Trade_WriteCircleOAM
And we'll fix a similar tile offset issue to properly handle the 2 frames of animation. This loop that we are modifying is also used for the circle that surrounds the mon during the animation, and we don't want to disrupt the circle's OAM animation, so we need to split this into two loops:
Trade_AnimCircledMon:
; Cycles between the two animation frames of the mon party sprite, cycles
; between a circle and an oval around the mon sprite, and makes the cable flash.
push de
push bc
push hl
ldh a, [rBGP]
xor $3c ; make link cable flash
ldh [rBGP], a
ld hl, wShadowOAMSprite00TileID
ld de, $4
- ld c, $14
-.loop
+ ld c, $4
+.mon_loop
+ ld a, [hl]
+ xor 2
+ ld [hl], a
+ add hl, de
+ dec c
+ jr nz, .mon_loop
+ ld c, $10
+.circle_loop
ld a, [hl]
xor ICONOFFSET
ld [hl], a
add hl, de
dec c
- jr nz, .loop
+ jr nz, .circle_loop
pop hl
pop bc
pop de
ret
The custom party mon sprites should now work correctly in all contexts:
Optional: By default, the party menu and nickname screen only use 3 distinct colors for the sprites (white, white, light, and black), but the custom sprites were designed to take advantage of 4 colors (white, light, dark, and black).
To use all 4 colors in the party menu, make this change to RedrawPartyMenu_
in engine/menus/party_menu.asm:
ld a, 1
ldh [hAutoBGTransferEnabled], a
call Delay3
- jp GBPalNormal
+ ld a, %11100100 ; 3210
+ ldh [rBGP], a
+ ldh [rOBP0], a
+ ret
.printItemUseMessage
and $0F
ld hl, PartyMenuItemUseMessagePointers
and make a similar change to DisplayNamingScreen
in engine/menus/naming_screen.asm:
ld [wAnimCounter], a
.selectReturnPoint
call PrintAlphabet
- call GBPalNormal
+ ld a, %11100100 ; 3210
+ ldh [rBGP], a
+ ldh [rOBP0], a
.ABStartReturnPoint
ld a, [wNamingScreenSubmitName]
and a
The sprites will look more detailed and true to the intentions of the spriters that created them: