Use unique colors for each thrown Poké Ball - pret/pokecrystal GitHub Wiki

Gen 2 showed colored Poké Balls in battle, but the colors were picked from a limited set of six. Gen 3 started showing each Poké Ball with its unique graphics and colors.

This tutorial will add unique colors for each type of Ball, although they'll all still use the same graphic.

(The code for this feature was adapted from Sour Crystal.)

Note: For older versions of the pokecrystal disassembly, the 'BATTLE_ANIM_GFX_POKE_BALL' AnimObjGFX index was 'ANIM_GFX_POKE_BALL'. It was changed on Sep 18, 2023.

Contents

  1. Define unique colors for each Ball
  2. Make every Ball use PAL_BATTLE_OB_RED
  3. Load the unique colors into the palette

1. Define unique colors for each Ball

Edit data/battle_anims/ball_colors.asm:

 ; colors of balls thrown in battle

 BallColors:
-	db MASTER_BALL, PAL_BATTLE_OB_GREEN
-	db ULTRA_BALL,  PAL_BATTLE_OB_YELLOW
-	db GREAT_BALL,  PAL_BATTLE_OB_BLUE
-	db POKE_BALL,   PAL_BATTLE_OB_RED
-	db HEAVY_BALL,  PAL_BATTLE_OB_GRAY
-	db LEVEL_BALL,  PAL_BATTLE_OB_BROWN
-	db LURE_BALL,   PAL_BATTLE_OB_BLUE
-	db FAST_BALL,   PAL_BATTLE_OB_BLUE
-	db FRIEND_BALL, PAL_BATTLE_OB_YELLOW
-	db MOON_BALL,   PAL_BATTLE_OB_GRAY
-	db LOVE_BALL,   PAL_BATTLE_OB_RED
-	db -1,          PAL_BATTLE_OB_GRAY
+	db MASTER_BALL
+	RGB 31,31,31, 20,08,23
+	db ULTRA_BALL
+	RGB 31,31,31, 10,10,12
+	db GREAT_BALL
+	RGB 31,31,31, 09,13,30
+	db POKE_BALL
+	RGB 31,31,31, 30,08,05
+	db HEAVY_BALL
+	RGB 31,31,31, 06,10,12
+	db LEVEL_BALL
+	RGB 31,31,31, 30,24,00
+	db LURE_BALL
+	RGB 31,31,31, 04,14,30
+	db FAST_BALL
+	RGB 31,31,31, 31,16,04
+	db FRIEND_BALL
+	RGB 31,31,31, 04,17,04
+	db MOON_BALL
+	RGB 31,31,31, 07,19,25
+	db LOVE_BALL
+	RGB 31,31,31, 30,11,22
+	db PARK_BALL
+	RGB 31,31,31, 18,18,05
+	db -1 ; end
+	RGB 31,31,31, 16,16,16

Each Ball used to use one of the palettes meant for move animations (since throwing a Ball is technically an animation): gray, yellow, red, green, blue, or brown. Here we've replaced each PAL_BATTLE_OB_* color index with a pair of RGB colors: the first for the bottom half of the Ball, the second for the top half.

Here's how they'll look:

Screenshot

We just changed the BallColors table to assign each Ball a pair of actual RGB colors, instead of a palette index. So putting back the palette index will be the next step.

2. Make every Ball use PAL_BATTLE_OB_RED

Edit engine/battle_anims/functions.asm:

 GetBallAnimPal:
-	ld hl, BallColors
-	ldh a, [rSVBK]
-	push af
-	ld a, BANK(wCurItem)
-	ldh [rSVBK], a
-	ld a, [wCurItem]
-	ld e, a
-	pop af
-	ldh [rSVBK], a
-.IsInArray:
-	ld a, [hli]
-	cp -1
-	jr z, .load
-	cp e
-	jr z, .load
-	inc hl
-	jr .IsInArray
-
-.load
-	ld a, [hl]
 	ld hl, BATTLEANIMSTRUCT_PALETTE
 	add hl, bc
-	ld [hl], a
+	ld [hl], PAL_BATTLE_OB_RED
 	ret
-
-INCLUDE "data/battle_anims/ball_colors.asm"

Since we know every Ball will use the same palette index, the next step is to replace that palette's colors with the correct Ball-specific ones at the right time.

3. Load the unique colors into the palette

Edit engine/battle_anims/helpers.asm:

 LoadBattleAnimGFX:
 	push hl
+	cp BATTLE_ANIM_GFX_POKE_BALL
+	call z, .LoadBallPalette
 	ld l, a
 	ld h, 0
 	add hl, hl
 	add hl, hl
 	ld de, AnimObjGFX
 	add hl, de
 	ld c, [hl]
 	inc hl
 	ld b, [hl]
 	inc hl
 	ld a, [hli]
 	ld h, [hl]
 	ld l, a
 	pop de
 	push bc
 	call DecompressRequest2bpp
 	pop bc
 	ret
+
+.LoadBallPalette:
+	; save the current WRAM bank
+	ld a, [rSVBK]
+	push af
+	; switch to the WRAM bank of wCurItem so we can read it
+	ld a, BANK(wCurItem)
+	ld [rSVBK], a
+	; store the current item in b
+	ld a, [wCurItem]
+	ld b, a
+	; seek for the BallColors entry matching the current item
+	ld hl, BallColors
+.loop
+	ld a, [hli]
+	cp b ; did we find the current ball?
+	jr z, .done
+	cp -1 ; did we reach the end of the list?
+	jr z, .done
+rept PAL_COLOR_SIZE * 2
+	inc hl ; skip over the two RGB colors to the next entry
+endr
+	jr .loop
+.done
+	; switch to the WRAM bank of wOBPals2 so we can write to it
+	ld a, BANK(wOBPals2)
+	ld [rSVBK], a
+	; load the RGB colors into the middle two colors of PAL_BATTLE_OB_RED
+	ld de, wOBPals2 palette PAL_BATTLE_OB_RED color 1
+rept PAL_COLOR_SIZE * 2 - 1
+	ld a, [hli]
+	ld [de], a
+	inc de
+endr
+	ld a, [hl]
+	ld [de], a
+	; apply the updated colors to the palette RAM
+	ld a, $1
+	ldh [hCGBPalUpdate], a
+	; restore the previous WRAM bank
+	pop af
+	ld [rSVBK], a
+	; restore the graphics index to be loaded
+	ld a, BATTLE_ANIM_GFX_POKE_BALL
+	ret
+
+INCLUDE "data/battle_anims/ball_colors.asm"

LoadBattleAnimGFX is called with an BATTLE_ANIM_GFX_* constant in a; it loads the appropriate graphics for that constant in preparation for animating the move. If we inspect data/moves/animations.asm, we can see that BATTLE_ANIM_GFX_POKE_BALL is used for throwing Poké Balls, and only for throwing Poké Balls; so if we know it's being loaded, that's the right time to load our unique colors too.

Now we can see the unique colors in action:

Screenshot