5 How to add a new ability
Thomas Winwood edited this page 2020-05-15 08:42:25 +01:00

This tutorial is for how to add a new ability. As an example, we'll add Snow Warning.

Contents

  1. Define an ability constant
  2. Give it a name and description
  3. Make it do something
    1. Add a new weather constant
    2. Add a new battle script
    3. Add a new battle message
    4. Implement the ability

1. Define an ability constant

Edit include/constants/abilities.h:

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

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 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:

            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:

// 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:

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:

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:

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

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:

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

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.

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 and implement our new ability:

            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.