Skip to content

Add All Unique Party Menu Sprite Icons

dannye edited this page Jan 16, 2024 · 6 revisions

Intro

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.

Solution

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

party_mon_sprites

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:

party-menu nickname-screen trade-screen


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:

party-menu-2

Clone this wiki locally