1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
|
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](https://github.com/SoupPotato/Sourcrystal/).)
## Contents
1. [Define unique colors for each Ball](#1-define-unique-colors-for-each-ball)
2. [Make every Ball use `PAL_BATTLE_OB_RED`](#2-make-every-ball-use-pal_battle_ob_red)
3. [Load the unique colors into the palette](#3-load-the-unique-colors-into-the-palette)
## 1. Define unique colors for each Ball
Edit [data/battle_anims/ball_colors.asm](../blob/master/data/battle_anims/ball_colors.asm):
```diff
; 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:

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](../blob/master/engine/battle_anims/functions.asm):
```diff
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](../blob/master/engine/battle_anims/helpers.asm):
```diff
LoadBattleAnimGFX:
push hl
+ cp 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, ANIM_GFX_POKE_BALL
+ ret
+
+INCLUDE "data/battle_anims/ball_colors.asm"
```
`LoadBattleAnimGFX` is called with an `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](../blob/master/data/moves/animations.asm), we can see that `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:

|