summaryrefslogtreecommitdiff
path: root/How-to-add-a-new-ability.md
blob: 621f058966d01f226a2f3f2b6707609fd038f623 (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
This tutorial is for how to add a new ability. As an example, we'll add Snow Warning.


## Contents

1. [Define an ability constant](#1-define-an-ability-constant)
2. [Give it a name and description](#2-give-it-a-name-and-description)
3. [Make it do something](#3-make-it-do-something)
    1. [Add a new weather constant](#i-add-a-new-weather-constant)
    2. [Add a new battle script](#ii-add-a-new-battle-script)
    3. [Add a new battle message](#iii-add-a-new-battle-message)
    3. [Implement the ability](#iv-implement-the-ability)


## 1. Define an ability constant

Edit [include/constants/abilities.h](../blob/master/include/constants/abilities.h):

```diff
#ifndef GUARD_CONSTANTS_ABILITIES_H
#define GUARD_CONSTANTS_ABILITIES_H

#define ABILITY_NONE 0
#define ABILITY_STENCH 1
...
#define ABILITY_CACOPHONY 76
#define ABILITY_AIR_LOCK 77
+#define ABILITY_SNOW_WARNING 78

-#define ABILITIES_COUNT 78
+#define ABILITIES_COUNT 79

#endif  // GUARD_CONSTANTS_ABILITIES_H
```


## 2. Give it a name and description

Edit [src/data/text/abilities.h](../blob/master/src/data/text/abilities.h):

```diff
static const u8 sNoneDescription[] = _("No special ability.");
static const u8 sStenchDescription[] = _("Helps repel wild POKéMON.");
...
static const u8 sCacophonyDescription[] = _("Avoids sound-based moves.");
static const u8 sAirLockDescription[] = _("Negates weather effects.");
+static const u8 sSnowWarningDescription[] = _("Summons hail in battle.");

const u8 gAbilityNames[ABILITIES_COUNT][ABILITY_NAME_LENGTH + 1] =
{
    [ABILITY_NONE] = _("-------"),
    [ABILITY_STENCH] = _("STENCH"),
...
    [ABILITY_CACOPHONY] = _("CACOPHONY"),
    [ABILITY_AIR_LOCK] = _("AIR LOCK"),
+    [ABILITY_SNOW_WARNING] = _("SNOW WARNING"),
};

const u8 *const gAbilityDescriptionPointers[ABILITIES_COUNT] =
{
    [ABILITY_NONE] = sNoneDescription,
    [ABILITY_STENCH] = sStenchDescription,
...
    [ABILITY_CACOPHONY] = sCacophonyDescription,
    [ABILITY_AIR_LOCK] = sAirLockDescription,
+    [ABILITY_SNOW_WARNING] = sSnowWarningDescription,
};
```

If you want, you can edit [src/data/pokemon/base_stats.h](../blob/master/src/data/pokemon/base_stats.h) to give one of the starter Pokemon the new ability, run up a build and test it ingame - the new ability isn't set up to do anything, but it will show up correctly in the stats screen.


## 3. Make it do something

This will be radically different for each ability - think about how your ability works and what similar abilities exist in Gen 3, then look into how those abilities are implemented. We know that Snow Warning works the same as Drizzle, Drought and Sand Stream, and searching for those finds us the function `AbilityBattleEffects` in [src/battle_util.c](../blob/master/src/battle_util.c):

```c
            case ABILITY_DRIZZLE:
                if (!(gBattleWeather & WEATHER_RAIN_PERMANENT))
                {
                    gBattleWeather = (WEATHER_RAIN_PERMANENT | WEATHER_RAIN_TEMPORARY);
                    BattleScriptPushCursorAndCallback(BattleScript_DrizzleActivates);
                    gBattleScripting.battler = battler;
                    effect++;
                }
                break;
            case ABILITY_SAND_STREAM:
                if (!(gBattleWeather & WEATHER_SANDSTORM_PERMANENT))
                {
                    gBattleWeather = (WEATHER_SANDSTORM_PERMANENT | WEATHER_SANDSTORM_TEMPORARY);
                    BattleScriptPushCursorAndCallback(BattleScript_SandstreamActivates);
                    gBattleScripting.battler = battler;
                    effect++;
                }
                break;
            case ABILITY_DROUGHT:
                if (!(gBattleWeather & WEATHER_SUN_PERMANENT))
                {
                    gBattleWeather = (WEATHER_SUN_PERMANENT | WEATHER_SUN_TEMPORARY);
                    BattleScriptPushCursorAndCallback(BattleScript_DroughtActivates);
                    gBattleScripting.battler = battler;
                    effect++;
                }
                break;
```

From this we learn the following.

1. The game makes a distinction between permanent and temporary weather conditions, so we want to make sure the same distinction is made for hail.
2. The game executes a unique battle script for each condition, so we'll need one for Snow Warning as well.

Let's deal with both of those and come back to `AbilityBattleEffects` later.


### i. Add a new weather constant

Edit [include/constants/battle.h](../blob/master/include/constants/battle.h):

```diff
// Battle Weather flags
#define WEATHER_RAIN_TEMPORARY      (1 << 0)
#define WEATHER_RAIN_DOWNPOUR       (1 << 1)  // unused
#define WEATHER_RAIN_PERMANENT      (1 << 2)
#define WEATHER_RAIN_ANY            (WEATHER_RAIN_TEMPORARY | WEATHER_RAIN_DOWNPOUR | WEATHER_RAIN_PERMANENT)
#define WEATHER_SANDSTORM_TEMPORARY (1 << 3)
#define WEATHER_SANDSTORM_PERMANENT (1 << 4)
#define WEATHER_SANDSTORM_ANY       (WEATHER_SANDSTORM_TEMPORARY | WEATHER_SANDSTORM_PERMANENT)
#define WEATHER_SUN_TEMPORARY       (1 << 5)
#define WEATHER_SUN_PERMANENT       (1 << 6)
#define WEATHER_SUN_ANY             (WEATHER_SUN_TEMPORARY | WEATHER_SUN_PERMANENT)
-#define WEATHER_HAIL                (1 << 7)
+#define WEATHER_HAIL_TEMPORARY      (1 << 7)
+#define WEATHER_HAIL_PERMANENT      (1 << 8)
-#define WEATHER_HAIL_ANY            (WEATHER_HAIL)
+#define WEATHER_HAIL_ANY            (WEATHER_HAIL_TEMPORARY | WEATHER_HAIL_PERMANENT)
#define WEATHER_ANY                 (WEATHER_RAIN_ANY | WEATHER_SANDSTORM_ANY | WEATHER_SUN_ANY | WEATHER_HAIL_ANY)
```

We've defined a bit for permanent hail, so we need to ensure that existing references to hail are correctly updated. Edit [src/battle_script_commands.c](../blob/master/src/battle_script_commands.c):

```diff
static void Cmd_sethail(void)
{
    if (gBattleWeather & WEATHER_HAIL_ANY)
    {
        gMoveResultFlags |= MOVE_RESULT_MISSED;
        gBattleCommunication[MULTISTRING_CHOOSER] = 2;
    }
    else
    {
-        gBattleWeather = WEATHER_HAIL;
+        gBattleWeather = WEATHER_HAIL_TEMPORARY;
        gBattleCommunication[MULTISTRING_CHOOSER] = 5;
        gWishFutureKnock.weatherDuration = 5;
    }

    gBattlescriptCurrInstr++;
}
```

And [src/battle_util.c](../blob/master/src/battle_util.c):

```diff
u8 DoFieldEndTurnEffects(void)
{
...
        case ENDTURN_HAIL:
            if (gBattleWeather & WEATHER_HAIL_ANY)
            {
-                if (--gWishFutureKnock.weatherDuration == 0)
+                if (!(gBattleWeather & WEATHER_HAIL_PERMANENT) && --gWishFutureKnock.weatherDuration == 0)
                {
-                    gBattleWeather &= ~WEATHER_HAIL;
+                    gBattleWeather &= ~WEATHER_HAIL_TEMPORARY;
                    gBattlescriptCurrInstr = BattleScript_SandStormHailEnds;
                }
                else
                {
                    gBattlescriptCurrInstr = BattleScript_DamagingWeatherContinues;
                }

                gBattleScripting.animArg1 = B_ANIM_HAIL_CONTINUES;
                gBattleCommunication[MULTISTRING_CHOOSER] = 1;
                BattleScriptExecute(gBattlescriptCurrInstr);
                effect++;
            }
            gBattleStruct->turnCountersTracker++;
            break;
...
}
```


### ii. Add a new battle script

Edit [include/battle_scripts.h](../blob/master/include/battle_scripts.h):

```diff
#ifndef GUARD_BATTLE_SCRIPTS_H
#define GUARD_BATTLE_SCRIPTS_H

extern const u8 BattleScript_HitFromCritCalc[];
extern const u8 BattleScript_MoveEnd[];
...
extern const u8 BattleScript_ActionGetNear[];
extern const u8 BattleScript_ActionThrowPokeblock[];
+extern const u8 BattleScript_SnowWarningActivates[];

#endif // GUARD_BATTLE_SCRIPTS_H
```

This exposes the symbol `BattleScript_SnowWarningActivates` to C, but battle scripts are actually written in assembler macros. Open [data/battle_scripts_1.s](../blob/master/data/battle_scripts_1.s):

```
BattleScript_DrizzleActivates::
	pause 0x20
	printstring STRINGID_PKMNMADEITRAIN
	waitstate
	playanimation BS_BATTLER_0, B_ANIM_RAIN_CONTINUES, NULL
	call BattleScript_WeatherFormChanges
	end3

...

BattleScript_SandstreamActivates::
	pause 0x20
	printstring STRINGID_PKMNSXWHIPPEDUPSANDSTORM
	waitstate
	playanimation BS_BATTLER_0, B_ANIM_SANDSTORM_CONTINUES, NULL
	call BattleScript_WeatherFormChanges
	end3

...

BattleScript_DroughtActivates::
	pause 0x20
	printstring STRINGID_PKMNSXINTENSIFIEDSUN
	waitstate
	playanimation BS_BATTLER_0, B_ANIM_SUN_CONTINUES, NULL
	call BattleScript_WeatherFormChanges
	end3
```

They're not in any obvious order, but the pattern is obvious enough that we can follow it. Add the following to the bottom of the file:

```
BattleScript_SnowWarningActivates::
	pause 0x20
	printstring STRINGID_PKMNSXWHIPPEDUPHAILSTORM
	waitstate
	playanimation BS_BATTLER_0, B_ANIM_HAIL_CONTINUES, NULL
	call BattleScript_WeatherFormChanges
	end3
```

We're nearly there, but first we need to deal with this `STRINGID` business.


### iii. Add a new battle message

Edit [include/constants/battle_string_ids.h](../blob/master/include/constants/battle_string_ids.h):

```diff
#ifndef GUARD_CONSTANTS_BATTLE_STRING_IDS_H
#define GUARD_CONSTANTS_BATTLE_STRING_IDS_H

-#define BATTLESTRINGS_COUNT     369
+#define BATTLESTRINGS_COUNT     370

#define BATTLESTRINGS_ID_ADDER  12 // all battlestrings have its ID + 12, because first 5 are reserved

#define STRINGID_INTROMSG       0
#define STRINGID_INTROSENDOUT   1
#define STRINGID_RETURNMON      2
#define STRINGID_SWITCHINMON    3
#define STRINGID_USEDMOVE       4
#define STRINGID_BATTLEEND      5

// todo: make some of those names less vague: attacker/target vs pkmn, etc.
#define STRINGID_TRAINER1LOSETEXT           12
#define STRINGID_PKMNGAINEDEXP              13
...
#define STRINGID_TRAINER1WINTEXT            379
#define STRINGID_TRAINER2WINTEXT            380
+#define STRINGID_PKMNSXWHIPPEDUPHAILSTORM   381

#endif // GUARD_CONSTANTS_BATTLE_STRING_IDS_H
```

This works essentially the same as in step 1 - we've defined a constant, now we need to write the actual message. Edit [src/battle_message.c](../blob/master/src/battle_message.c):

```diff
const u8 * const gBattleStringsTable[BATTLESTRINGS_COUNT] =
{
    [STRINGID_TRAINER1LOSETEXT - 12] = sText_Trainer1LoseText,
    [STRINGID_PKMNGAINEDEXP - 12] = sText_PkmnGainedEXP,
...
    [STRINGID_TRAINER1WINTEXT - 12] = sText_Trainer1WinText,
    [STRINGID_TRAINER2WINTEXT - 12] = sText_Trainer2WinText,
+    [STRINGID_PKMNSXWHIPPEDUPHAILSTORM - 12] = sText_PkmnsXWhippedUpHailstorm,
};
```

This file is a mess, so there's no obvious place to put the actual text; I chose to put it immediately following this line, since they're very similar.

```diff
static const u8 sText_PkmnsXWhippedUpSandstorm[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX}'s {B_SCR_ACTIVE_ABILITY}\nwhipped up a sandstorm!");
+static const u8 sText_PkmnsXWhippedUpHailstorm[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX}'s {B_SCR_ACTIVE_ABILITY}\nwhipped up a hailstorm!");
```


### iv. Implement the ability

Now we can return to [src/battle_util.c](../blob/master/src/battle_util.c) and implement our new ability:

```diff
            case ABILITY_DRIZZLE:
                if (!(gBattleWeather & WEATHER_RAIN_PERMANENT))
                {
                    gBattleWeather = (WEATHER_RAIN_PERMANENT | WEATHER_RAIN_TEMPORARY);
                    BattleScriptPushCursorAndCallback(BattleScript_DrizzleActivates);
                    gBattleScripting.battler = battler;
                    effect++;
                }
                break;
            case ABILITY_SAND_STREAM:
                if (!(gBattleWeather & WEATHER_SANDSTORM_PERMANENT))
                {
                    gBattleWeather = (WEATHER_SANDSTORM_PERMANENT | WEATHER_SANDSTORM_TEMPORARY);
                    BattleScriptPushCursorAndCallback(BattleScript_SandstreamActivates);
                    gBattleScripting.battler = battler;
                    effect++;
                }
                break;
            case ABILITY_DROUGHT:
                if (!(gBattleWeather & WEATHER_SUN_PERMANENT))
                {
                    gBattleWeather = (WEATHER_SUN_PERMANENT | WEATHER_SUN_TEMPORARY);
                    BattleScriptPushCursorAndCallback(BattleScript_DroughtActivates);
                    gBattleScripting.battler = battler;
                    effect++;
                }
                break;
+            case ABILITY_SNOW_WARNING:
+                if (!(gBattleWeather & WEATHER_HAIL_PERMANENT))
+                {
+                    gBattleWeather = (WEATHER_HAIL_PERMANENT | WEATHER_HAIL_TEMPORARY);
+                    BattleScriptPushCursorAndCallback(BattleScript_SnowWarningActivates);
+                    gBattleScripting.battler = battler;
+                    effect++;
+                }
+                break;
```

If you now build and test the game as before, when you send out the Pokemon with Snow Warning it will summon a hailstorm that lasts for the remainder of the battle.