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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
|
In Gen 2 (and Gen 3), some types were always physical and some were always special. Here's how to implement the physical/special split from Gen 4 and up.
(The code for this feature was adapted from [Pokémon Fractal](https://gitgud.io/pfero/axyllagame/) (the Axyllia region) and [Polished Crystal](https://github.com/Rangi42/polishedcrystal).)
## Contents
1. [Define new constants](#1-define-new-constants)
2. [Update moves with their categories](#2-update-moves-with-their-categories)
3. [Mask out the category in `PrintMoveType`](#3-mask-out-the-category-in-printmovetype)
4. [Mask out the category in nine places in the battle engine](#4-mask-out-the-category-in-nine-places-in-the-battle-engine)
5. [Make Hidden Power special](#5-make-hidden-power-special)
6. [Update the AI to understand categories](#6-update-the-ai-to-understand-categories)
7. [Support printing category names](#7-support-printing-category-names)
8. [Display categories in battle](#8-display-categories-in-battle)
9. [Display categories in the Move screen](#9-display-categories-in-the-move-screen)
## 1. Define new constants
Edit [constants/type_constants.asm](../blob/master/constants/type_constants.asm):
```diff
const_def
-
-PHYSICAL EQU const_value
const NORMAL
const FIGHTING
const FLYING
const POISON
const GROUND
const ROCK
const BIRD
const BUG
const GHOST
const STEEL
UNUSED_TYPES EQU const_value
const_next 19
const CURSE_TYPE
UNUSED_TYPES_END EQU const_value
-SPECIAL EQU const_value
const FIRE
const WATER
const GRASS
const ELECTRIC
const PSYCHIC_TYPE
const ICE
const DRAGON
const DARK
TYPES_END EQU const_value
+
+TYPE_MASK EQU %00111111
+PHYSICAL EQU %01000000
+SPECIAL EQU %10000000
+STATUS EQU %11000000
```
We're going to store each move's type and category in the same byte. This works well for two reasons:
1. A byte has eight bits. Two bits can store four values, which is enough for the three categories; and the remaining six can store 64 values, which is more than enough for all the types, even with those unused middle ones. We'll just have to be careful to mask out the category bits when dealing with types alone.
2. Throughout the code, moves' categories are checked by comparing their type value with the `SPECIAL` constant; values less than it are physical, otherwise they're special. We're keeping `PHYSICAL` < `SPECIAL`, so those checks will all still work. The category bits are higher than the type bits, so the type won't interfere with this relation; every `SPECIAL` + type combination will be greater than every `PHYSICAL` + type one.
## 2. Update moves with their categories
Edit [data/moves/moves.asm](../blob/master/data/moves/moves.asm):
```diff
move: MACRO
db \1 ; animation
db \2 ; effect
db \3 ; power
- db \4 ; type
- db \5 percent ; accuracy
- db \6 ; pp
- db \7 percent ; effect chance
+ db \4 | \5 ; type
+ db \6 percent ; accuracy
+ db \7 ; pp
+ db \8 percent ; effect chance
ENDM
Moves:
; entries correspond to constants/move_constants.asm
- move POUND, EFFECT_NORMAL_HIT, 40, NORMAL, 100, 35, 0
- ...
- move BEAT_UP, EFFECT_BEAT_UP, 10, DARK, 100, 10, 0
+ move POUND, EFFECT_NORMAL_HIT, 40, NORMAL, PHYSICAL, 100, 35, 0
+ ...
+ move BEAT_UP, EFFECT_BEAT_UP, 10, DARK, PHYSICAL, 100, 10, 0
```
You'll have to assign the right category—`PHYSICAL`, `SPECIAL`, or `STATUS`—to all 251 moves, right after their types. There's a file which already does this with the default pokecrystal moves [here](https://pastebin.com/ZnX1imv9). (Note: that file also changes Curse's type from `CURSE_T` to `GHOST`.)
## 3. Mask out the category in `PrintMoveType`
Edit [engine/pokemon/types.asm](../blob/master/engine/pokemon/types.asm):
```diff
PrintMoveType:
; Print the type of move b at hl.
push hl
ld a, b
dec a
ld bc, MOVE_LENGTH
ld hl, Moves
call AddNTimes
ld de, wStringBuffer1
ld a, BANK(Moves)
call FarCopyBytes
ld a, [wStringBuffer1 + MOVE_TYPE]
+ and TYPE_MASK
pop hl
```
This is the first of many times we'll have to add `and TYPE_MASK` somewhere.
## 4. Mask out the category in nine places in the battle engine
First, edit [engine/battle/effect_commands.asm](../blob/master/engine/battle/effect_commands.asm). There are *five* places in the code where we need to do this:
```diff
ld a, BATTLE_VARS_MOVE_TYPE
call GetBattleVar
+ and TYPE_MASK
```
1. `BattleCommand_Stab` (actually, this usage calls `GetBattleVarAddr` instead of `GetBattleVar`)
2. `BattleCommand_Stab` again (although this one calls `GetBattleVar`)
3. `CheckTypeMatchup`
4. `BattleCommand_DamageCalc`
5. `CheckMoveTypeMatchesTarget`
Just use your text editor to find all five occurrences of "`BATTLE_VARS_MOVE_TYPE`" in the file, and make that one-line change to all of them.
Next, edit [engine/battle/move_effects/conversion.asm](../blob/master/engine/battle/move_effects/conversion.asm):
```diff
ld hl, Moves + MOVE_TYPE
call GetMoveAttr
+ and TYPE_MASK
```
Edit [engine/battle/move_effects/conversion2.asm](../blob/master/engine/battle/move_effects/conversion2.asm):
```diff
ld hl, Moves + MOVE_TYPE
call GetMoveAttr
+ and TYPE_MASK
...
ld a, BATTLE_VARS_MOVE_TYPE
call GetBattleVarAddr
+ and TYPE_MASK
```
And edit [engine/battle/move_effects/thunder.asm](../blob/master/engine/battle/move_effects/thunder.asm):
```diff
ld a, BATTLE_VARS_MOVE_TYPE
call GetBattleVarAddr
+ and TYPE_MASK
```
That's nine additions of `and TYPE_MASK` to mask out the category bits and leave only the type.
## 5. Make Hidden Power special
Edit [engine/battle/hidden_power.asm](../blob/master/engine/battle/hidden_power.asm):
```diff
; Overwrite the current move type.
push af
ld a, BATTLE_VARS_MOVE_TYPE
call GetBattleVarAddr
pop af
+ or SPECIAL
ld [hl], a
```
If you're using an older version of pokecrystal, you may also have to edit another line to make sure Hidden Power's type is calculated correctly:
```diff
; Skip unused types
cp UNUSED_TYPES
jr c, .done
- add SPECIAL - UNUSED_TYPES
+ add UNUSED_TYPES_END - UNUSED_TYPES
```
## 6. Update the AI to understand categories
At this point the Physical/Special split *works*, technically, but for two things: the AI doesn't fully understand it, and the user interface doesn't show it. We'll take care of the AI first.
Edit [engine/battle/ai/scoring.asm](../blob/master/engine/battle/ai/scoring.asm). There are a few unrelated changes to make here, so let's go over them one at a time.
```diff
AI_Types:
...
; Discourage this move if there are any moves
; that do damage of a different type.
push hl
push de
push bc
ld a, [wEnemyMoveStruct + MOVE_TYPE]
+ and TYPE_MASK
ld d, a
...
call AIGetEnemyMove
ld a, [wEnemyMoveStruct + MOVE_TYPE]
+ and TYPE_MASK
cp d
```
Here we're just masking out categories again.
```diff
AI_Smart_SpDefenseUp2:
...
; 80% chance to greatly encourage this move if
-; enemy's Special Defense level is lower than +2, and the player is of a special type.
+; enemy's Special Defense level is lower than +2,
+; and the player's Pokémon is Special-oriented.
cp BASE_STAT_LEVEL + 2
ret nc
- ld a, [wBattleMonType1]
- cp SPECIAL
- jr nc, .encourage
- ld a, [wBattleMonType2]
- cp SPECIAL
- ret c
+ push hl
+; Get the pointer for the player's Pokémon's base Attack
+ ld a, [wBattleMonSpecies]
+ ld hl, BaseData + BASE_ATK
+ ld bc, BASE_DATA_SIZE
+ call AddNTimes
+; Get the Pokémon's base Attack
+ ld a, BANK(BaseData)
+ call GetFarByte
+ ld d, a
+; Get the pointer for the player's Pokémon's base Special Attack
+ ld bc, BASE_SAT - BASE_ATK
+ add hl, bc
+; Get the Pokémon's base Special Attack
+ ld a, BANK(BaseData)
+ call GetFarByte
+ pop hl
+; If its base Attack is greater than its base Special Attack,
+; don't encourage this move.
+ cp d
+ ret c
.encourage
call AI_80_20
ret c
dec [hl]
dec [hl]
ret
```
This routine used to encourage the AI to use moves that raise its Special Defense if the player's Pokémon was of a Special type. Since physical/special categories are now independent of types, we've changed it to check whether the player's base Special Attack is at least as high as its base Attack.
```diff
AI_Smart_Encore:
...
push hl
ld a, [wEnemyMoveStruct + MOVE_TYPE]
+ and TYPE_MASK
ld hl, wEnemyMonType1
predef CheckTypeMatchup
```
Just masking out the category again.
```diff
AI_Smart_Curse:
...
ld a, [wBattleMonType1]
cp GHOST
jr z, .greatly_encourage
- cp SPECIAL
- ret nc
- ld a, [wBattleMonType2]
- cp SPECIAL
- ret nc
call AI_80_20
ret c
dec [hl]
dec [hl]
ret
```
This routine used to discourage the AI from using Curse if the player's Pokémon was of a Special type (since Curse raises the user's Defense, which is useless against special attacks). That's no longer meaningful, but it's not worth checking the player's base stats again.
## 7. Support printing category names
Create **data/types/category_names.asm**:
```diff
+CategoryNames:
+ dw .Physical
+ dw .Special
+ dw .Status
+
+.Physical: db "PHYSICAL@"
+.Special: db "SPECIAL@"
+.Status: db "STATUS@"
```
Create **engine/pokemon/categories.asm**:
```diff
+GetMoveCategoryName:
+; Copy the category name of move b to wStringBuffer1.
+
+ ld a, b
+ dec a
+ ld bc, MOVE_LENGTH
+ ld hl, Moves + MOVE_TYPE
+ call AddNTimes
+ ld a, BANK(Moves)
+ call GetFarByte
+
+; Mask out the type
+ and ~TYPE_MASK
+; Shift the category bits into the range 0-2
+ rlc a
+ rlc a
+ dec a
+
+ ld hl, CategoryNames
+ ld e, a
+ ld d, 0
+ add hl, de
+ add hl, de
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld de, wStringBuffer1
+ ld bc, MOVE_NAME_LENGTH
+ jp CopyBytes
+
+INCLUDE "data/types/category_names.asm"
```
And edit [main.asm](../blob/master/main.asm):
```diff
INCLUDE "engine/pokemon/types.asm"
+INCLUDE "engine/pokemon/categories.asm"
```
This is based on the routines in [engine/pokemon/types.asm](../blob/master/engine/pokemon/types.asm).
## 8. Display categories in battle
Edit [engine/battle/core.asm](../blob/master/engine/battle/core.asm):
```diff
+ callfar UpdateMoveData
+ ld a, [wPlayerMoveStruct + MOVE_ANIM]
+ ld b, a
+ farcall GetMoveCategoryName
hlcoord 1, 9
- ld de, .Type
+ ld de, wStringBuffer1
call PlaceString
- hlcoord 7, 11
+ ld h, b
+ ld l, c
ld [hl], "/"
- callfar UpdateMoveData
ld a, [wPlayerMoveStruct + MOVE_ANIM]
ld b, a
hlcoord 2, 10
predef PrintMoveType
.done
ret
.Disabled:
db "Disabled!@"
-.Type:
- db "TYPE/@"
```
Instead of printing "TYPE/" in the move property box, we print the move's category.
## 9. Display categories in the Move screen
Edit [engine/pokemon/mon_menu.asm](../blob/master/engine/pokemon/mon_menu.asm) (or [engine/menus/start_menu.asm](../blob/master/engine/menus/start_menu.asm) in older versions of pokecrystal):
```diff
PlaceMoveData:
xor a
ldh [hBGMapMode], a
hlcoord 0, 10
ld de, String_MoveType_Top
call PlaceString
hlcoord 0, 11
ld de, String_MoveType_Bottom
call PlaceString
hlcoord 12, 12
ld de, String_MoveAtk
call PlaceString
+ ld a, [wCurSpecies]
+ ld b, a
+ farcall GetMoveCategoryName
+ hlcoord 1, 11
+ ld de, wStringBuffer1
+ call PlaceString
ld a, [wCurSpecies]
ld b, a
- hlcoord 2, 12
+ hlcoord 1, 12
+ ld [hl], "/"
+ inc hl
predef PrintMoveType
...
String_MoveType_Top:
- db "┌─────┐@
+ db "┌────────┐@"
String_MoveType_Bottom:
- db "│TYPE/└@"
+ db "│ └@"
```
```diff
.moving_move
ld a, " "
hlcoord 1, 11
- ld bc, 5
+ ld bc, 8
call ByteFill
hlcoord 1, 12
lb bc, 5, SCREEN_WIDTH - 2
call ClearBox
```
Again, instead of printing "TYPE/" in the move property box, we print the move's category. There's no room for the "/" after the category, so here it goes before the type.
Now we're done!

|