-
Notifications
You must be signed in to change notification settings - Fork 2.7k
How to add a new ability
This tutorial is for how to add a new ability. As an example, we'll add Snow Warning.
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
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.
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.
- The game makes a distinction between permanent and temporary weather conditions, so we want to make sure the same distinction is made for hail.
- 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.
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;
...
}
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.
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!");
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.