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
|
This tutorial is for how to add a new move, allowing up to 255 moves. As an example, we'll add Nasty Plot.
## Contents
1. [Define a move constant](#1-define-a-move-constant)
2. [Give it a name and description](#2-give-it-a-name-and-description)
3. [Define its battle properties](#3-define-its-battle-properties)
4. [Define its animation](#4-define-its-animation)
5. [Let Pokémon learn the move](#5-let-pokémon-learn-the-move)
6. [Adding a 255th move](#6-adding-a-255th-move)
1. [Prepare move $FF](#61-prepare-move-ff)
2. [Swap move $FF with Struggle](#62-swap-move-ff-with-struggle)
3. [Remove Struggle from the `MetronomeExcepts` list](#63-remove-struggle-from-the-metronomeexcepts-list)
4. [Get rid of `NUM_ATTACKS + 1` checks](#64-get-rid-of-num_attacks--1-checks)
5. [Use $00 instead of $FF for Pursuit's effect](#65-use-00-instead-of-ff-for-pursuits-effect)
## 1. Define a move constant
Edit [constants/move_constants.asm](../blob/master/constants/move_constants.asm):
```diff
; move ids
; indexes for:
; - Moves (see data/moves/moves.asm)
; - MoveNames (see data/moves/names.asm)
; - MoveDescriptions (see data/moves/descriptions.asm)
; - BattleAnimations (see data/moves/animations.asm)
const_def
const NO_MOVE ; 00
const POUND ; 01
...
const BEAT_UP ; fb
+ const NASTY_PLOT ; fc
NUM_ATTACKS EQU const_value - 1
; Battle animations use the same constants as the moves up to this point
const_next $ff
const ANIM_SWEET_SCENT_2 ; ff
...
```
Move constants are actually a subset of battle animation constants. $01 to $FB are the 251 constants from `POUND` to `BEAT_UP`; then $FC, $FD, and $FE are unused; then starting at $FF, `ANIM_SWEET_SCENT_2` and above correspond to animations beyond the ones played for moves (throwing Poké Balls, showing confusion, etc). Anyway, those three unused values can all be used for new moves.
## 2. Give it a name and description
Edit [data/moves/names.asm](../blob/master/data/moves/names.asm):
```diff
MoveNames::
db "POUND@"
...
db "BEAT UP@"
+ db "NASTY PLOT@"
```
A name can be up to 12 characters long, plus a "@" at the end.
Now edit [data/moves/descriptions.asm](../blob/master/data/moves/descriptions.asm):
```diff
MoveDescriptions::
; entries correspond to move ids (see constants/move_constants.asm)
dw PoundDescription
...
dw BeatUpDescription
- dw MoveFCDescription
+ dw NastyPlotDescription
dw MoveFDDescription
dw MoveFEDescription
dw MoveFFDescription
dw Move00Description
-MoveFCDescription:
MoveFDDescription:
MoveFEDescription:
MoveFFDescription:
Move00Description:
db "?@"
...
BeatUpDescription:
db "Party #MON join"
next "in the attack.@"
+
+NastyPlotDescription:
+ db "Sharply increases"
+ next "user's SPCL.ATK.@"
```
A description has two lines, each with up to 18 characters, plus a "@" at the end.
## 3. Define its battle properties
Edit [data/moves/moves.asm](../blob/master/data/moves/moves.asm):
```diff
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 NASTY_PLOT, EFFECT_SP_ATK_UP_2, 0, DARK, 100, 20, 0
```
The `move` defines these properties:
- **animation:** Which animation to play when using the move. Remember, constants like `POUND` correspond to moves but also to battle animations, as we'll see later.
- **effect:** What effect the move has. Valid effects are in [constants/move_effect_constants.asm](../blob/master/constants/move_effect_constants.asm). Some exist that aren't used for any moves yet, like `EFFECT_SP_ATK_UP_2`.
- **power:** The base power. 0 for non-damaging moves; 1 for moves that do damage but not with the standard formula, like Seismic Toss, Counter, or Magnitude. (The AI uses this property to distinguish damaging and non-damaging moves.)
- **type:** The type.
- **accuracy:** The accuracy, from 1 to 100.
- **PP:** The PP, from 5 to 40. Sketch has 1 PP but it requires special-case code in some places; and 40 is the maximum because any more and PP Up could boost it out of bounds. (PP is stored in 6 bits, not a full byte, so cannot exceed 63.)
- **effect chance:** The chances of an effect triggering. Not applicable for all effects.
## 4. Define its animation
Edit [data/moves/animations.asm](../blob/master/data/moves/animations.asm):
```diff
BattleAnimations::
; entries correspond to constants/move_constants.asm
dw BattleAnim_0
dw BattleAnim_Pound
...
dw BattleAnim_BeatUp
- dw BattleAnim_252
+ dw BattleAnim_NastyPlot
dw BattleAnim_253
dw BattleAnim_254
dw BattleAnim_SweetScent2
; $100
dw BattleAnim_ThrowPokeBall
...
BattleAnim_0:
-BattleAnim_252:
BattleAnim_253:
BattleAnim_254:
BattleAnim_MirrorMove:
anim_ret
...
+BattleAnim_NastyPlot:
BattleAnim_PsychUp:
anim_1gfx ANIM_GFX_STATUS
anim_call BattleAnim_FollowEnemyFeet_0
anim_bgeffect ANIM_BG_1A, $0, $1, $20
anim_sound 0, 0, SFX_PSYBEAM
anim_obj ANIM_OBJ_PSYCH_UP, 44, 88, $0
anim_obj ANIM_OBJ_PSYCH_UP, 44, 88, $10
anim_obj ANIM_OBJ_PSYCH_UP, 44, 88, $20
anim_obj ANIM_OBJ_PSYCH_UP, 44, 88, $30
anim_wait 64
anim_incbgeffect ANIM_BG_1A
anim_call BattleAnim_ShowMon_0
anim_wait 16
anim_ret
```
Designing a new animation is beyond the scope of this tutorial. They require careful placement and timing of different elements, and the scripting system used to do this is [poorly understood](../blob/master/docs/battle_anim_commands.md). Here we're just reusing Psych Up's animation for Nasty Plot, since it looks appropriate.
## 5. Let Pokémon learn the move
By now the move fully exists—it might show up with Metronome—but no Pokémon can use it. So add it to level-up learnsets in [data/pokemon/evos_attacks.asm](../blob/master/data/pokemon/evos_attacks.asm), egg move sets in [data/pokemon/egg_moves.asm](../blob/master/data/pokemon/egg_moves.asm), or NPC trainers' parties in [data/trainers/parties.asm](../blob/master/data/trainers/parties.asm) (see the [new Pokémon](Add-a-new-Pokémon) and [new trainer](Add-a-new-trainer-class) tutorials for help with that). Or add a new TM for it, following [the tutorial](Add-a-new-TM).
I added `NASTY_PLOT` to these sets, based on their canon ones in later generations:
- [data/pokemon/evos_attacks.asm](../blob/master/data/pokemon/evos_attacks.asm): Meowth, Persian, Drowzee, Hypno, Mew, Pichu, Aipom, Slowking, Girafarig, Houndour, Houndoom
- [data/pokemon/egg_moves.asm](../blob/master/data/pokemon/egg_moves.asm): Zubat, Drowzee, Mr. Mime, Togepi, Misdreavus, Houndour, Smoochum
- [data/trainers/parties.asm](../blob/master/data/trainers/parties.asm): Karen's Houndoom

## 6. Adding a 255th move
It's pretty easy to replace the unused moves 252, 253, and 254 ($FC, $FD, and $FE) using the steps above. But move 255 ($FF) is trickier. The value $FF is often used as a special case in the code: it's the maximum value a single byte can have, it marks the end of lists, and using it like any other value can be difficult to impossible.
(If you're wondering why so many lists end with `db -1`, not `db $ff`, that's because the byte $FF can be 255 or −1 depending on context, due to [two's complement](https://en.wikipedia.org/wiki/Two%27s_complement) arithmetic.)
### 6.1. Prepare move $FF
Let's say the 255th move will be Fake Out. First, follow the steps as usual. Define `FAKE_OUT` after your move $FE; give it a name, description, and battle properties (`FAKE_OUT, EFFECT_FAKE_OUT, 40, NORMAL, 100, 10, 0`); give it an animation (it can share `BattleAnim_Tackle`); and add it to Pokémon learnsets.
Remember, this time we're introducing a new constant, not replacing an old one. So you also need to change `const_next $ff` to `const_next $100` for the battle animation constants, or just remove the `const_next` since there's no longer a gap between moves and battle animations. This means `ANIM_SWEET_SCENT_2` will be shifted from $FF to $100; `ANIM_THROW_POKE_BALL` from $100 to $101; and so on.
### 6.2. Swap move $FF with Struggle
Because $FF (aka 255, aka −1) is treated specially, it's not actually suitable as a move ID. Luckily, Struggle is a move that's treated specially itself: no Pokémon can learn it naturally. So go back to all the files you edited in the previous step, and swap the lines for Struggle ($A5) with the lines for Fake Out ($FF).
### 6.3. Remove Struggle from the `MetronomeExcepts` list
Now that Struggle is move $FF, including it in a list would end the list early. Usually moves would show up in various lists, but since Struggle is not a typical learnable move, it only shows up in one: `MetronomeExcepts`, the list of moves Metronome cannot copy.
Edit [data/moves/metronome_exception_moves.asm](../blob/master/data/moves/metronome_exception_moves.asm):
```diff
MetronomeExcepts:
db NO_MOVE
db METRONOME
- db STRUGGLE
db SKETCH
db MIMIC
db COUNTER
db MIRROR_COAT
db PROTECT
db DETECT
db ENDURE
db DESTINY_BOND
db SLEEP_TALK
db THIEF
db -1
```
We still don't want Metronome to copy Struggle, so edit [engine/battle/move_effects/metronome.asm](../blob/master/engine/battle/move_effects/metronome.asm):
```diff
; No invalid moves.
cp NUM_ATTACKS + 1
jr nc, .GetMove
+; No Struggle.
+ cp STRUGGLE
+ jr z, .GetMove
+
; None of the moves in MetronomeExcepts.
push af
ld de, 1
ld hl, MetronomeExcepts
call IsInArray
pop bc
jr c, .GetMove
```
### 6.4. Get rid of `NUM_ATTACKS + 1` checks
Some places in the code do `cp NUM_ATTACKS + 1` to check if a move ID is not too high. If we have 255 moves, then `NUM_ATTACKS` + 1 will be 256 ($100), which won't fit in one byte, so these checks will cause a build error. However, the checks will also be redundant since every move ID is valid, so we can remove them.
Edit [engine/battle/move_effects/metronome.asm](../blob/master/engine/battle/move_effects/metronome.asm) again:
```diff
-; No invalid moves.
- cp NUM_ATTACKS + 1
- jr nc, .GetMove
```
Edit [engine/events/battle_tower/battle_tower.asm](../blob/master/engine/events/battle_tower/battle_tower.asm):
```diff
ValidateBTParty:
; Check for and fix errors in party data
...
.dont_load
ld [wCurPartyLevel], a
ld hl, MON_MOVES
add hl, bc
ld d, NUM_MOVES - 1
ld a, [hli]
and a
- jr z, .not_move
- cp NUM_ATTACKS + 1
- jr nc, .not_move
- jr .valid_move
+ jr nz, .valid_move
-.not_move
dec hl
ld a, POUND
ld [hli], a
xor a
ld [hli], a
ld [hli], a
ld [hl], a
jr .done_moves
.valid_move
- ld a, [hl]
- cp NUM_ATTACKS + 1
- jr c, .next
- ld [hl], $0
-
-.next
- inc hl
+ ld a, [hli]
dec d
jr nz, .valid_move
```
Edit [engine/pokemon/correct_party_errors.asm](../blob/master/engine/pokemon/correct_party_errors.asm):
```diff
ld hl, wPartyMon1Moves
ld a, [wPartyCount]
ld b, a
.loop5
push hl
ld c, NUM_MOVES
ld a, [hl]
and a
- jr z, .invalid_move
- cp NUM_ATTACKS + 1
- jr c, .moves_loop
+ jr nz, .moves_loop
-.invalid_move
ld [hl], POUND
.moves_loop
ld a, [hl]
and a
- jr z, .fill_invalid_moves
- cp NUM_ATTACKS + 1
- jr c, .next_move
+ jr nz, .next_move
.fill_invalid_moves
...
```
And edit [mobile/mobile_5c.asm](../blob/master/mobile/mobile_5c.asm):
```diff
CheckBTMonMovesForErrors:
ld c, BATTLETOWER_PARTY_LENGTH
ld hl, wBT_OTTempMon1Moves
.loop
push hl
- ld a, [hl]
- cp NUM_ATTACKS + 1
- jr c, .okay
- ld a, POUND
- ld [hl], a
-
-.okay
- inc hl
+ ld a, [hli]
ld b, NUM_MOVES - 1
.loop2
ld a, [hl]
and a
- jr z, .loop3
- cp NUM_ATTACKS + 1
- jr c, .next
+ jr nz, .next
.loop3
...
```
### 6.5. Use $00 instead of $FF for Pursuit's effect
There's one more way that $FF as a move ID is special. When Pursuit attacks a Pokémon that's switching out, the value $FF is used to end that attack early. Now that $FF is a valid move, we'll need to use a different value; `NO_MOVE` ($00) works.
Edit [engine/battle/core.asm](../blob/master/engine/battle/core.asm):
```diff
PursuitSwitch:
...
ld a, BATTLE_VARS_MOVE
call GetBattleVarAddr
- ld a, $ff
+ xor a ; NO_MOVE
ld [hl], a
...
```
And edit [engine/battle/effect_commands.asm](../blob/master/engine/battle/effect_commands.asm):
```diff
CheckTurn:
BattleCommand_CheckTurn:
; checkturn
; Repurposed as hardcoded turn handling. Useless as a command.
-; Move $ff immediately ends the turn.
+; NO_MOVE immediately ends the turn.
ld a, BATTLE_VARS_MOVE
call GetBattleVar
- inc a
+ and a ; NO_MOVE?
jp z, EndTurn
...
```
Now you can have 255 moves, as long as the last one is Struggle!
|