summaryrefslogtreecommitdiff
path: root/Add-a-new-move.md
blob: b31ebff9d3acf35ff7c628947b0f22add2dcb139 (plain)
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

![Screenshot](screenshots/nasty-plot.png)


## 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!