-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Add Physical Special Split
This tutorial is for adding the per-move physical/special split that was implemented starting in Diamond/Pearl.
- Adding a byte to the move struct
- Modifying the physicality check macro
- Modifying the damage calculation logic
- Adding the physicality byte to moves
- A Note on AI Scripts
The struct that defines the properties of a move are located in include/pokemon.h, so we will be modifying that file to include a byte that determines whether the move is physical or special. We will also be adding some macros to make the values more descriptive. Before editing, the struct looks like this:
struct BattleMove
{
u8 effect;
u8 power;
u8 type;
u8 accuracy;
u8 pp;
u8 secondaryEffectChance;
u8 target;
s8 priority;
u8 flags;
};
All we need to do is add another field to determine if the move is physical or special. I will be using the name "physicality" for this, but the name can be anything as long as it is consistent. We will also add some new macros for the possible values of this field. Again, the values can be anything as long as consistency is maintained, but I will be using 0 for physical, 1 for special, and 2 for other/status. After adding these, our modified struct looks like this:
#define MOVE_PHYSICALITY_PHYSICAL 0
#define MOVE_PHYSICALITY_SPECIAL 1
#define MOVE_PHYSICALITY_OTHER 2
struct BattleMove
{
u8 effect;
u8 power;
u8 type;
u8 accuracy;
u8 pp;
u8 secondaryEffectChance;
u8 target;
s8 priority;
u8 flags;
u8 physicality;
};
The game uses a function-style macro to check whether a move is physical or special based on its type. The macro lives in include/battle.h and looks like this:
#define IS_TYPE_PHYSICAL(moveType)(moveType < TYPE_MYSTERY)
#define IS_TYPE_SPECIAL(moveType)(moveType > TYPE_MYSTERY)
The macro simply looks at the move and compares the index of its type to TYPE_MYSTERY, which is 0x9. Types are split down the middle by TYPE_MYSTERY; all physical types are before it, and all special types are after it, so a simple comparison does the job. We will change this to look at the physicality byte of the move instead of the move's type. For my definition, it looks like this:
#define IS_TYPE_PHYSICAL(move)(move.physicality == MOVE_PHYSICALITY_PHYSICAL)
#define IS_TYPE_SPECIAL(move)(move.physicality == MOVE_PHYSICALITY_SPECIAL)
Next, we need to update a few checks in the function for calculating the base damage of a move. First, we will look at src/pokemon.c. The function we want here is:
s32 CalculateBaseDamage(struct BattlePokemon *attacker, struct BattlePokemon *defender, u32 move, u16 sideStatus, u16 powerOverride, u8 typeOverride, u8 battlerIdAtk, u8 battlerIdDef)
First, we need to look at the block of code that begins like:
for (i = 0; i < ARRAY_COUNT(sHoldEffectToType); i++)
{
if (attackerHoldEffect == sHoldEffectToType[i][0]
&& type == sHoldEffectToType[i][1])
This is the code used to apply bonuses from type-boosting items like Miracle Seed, Charcoal, Magnet, etc. The original function checks whether the type is physical or special and then only boosts the corresponding stat. Since we've made the physical/special split on a per-move basis, we'll just boost both stats to ensure the move gets boosted. This is as simple as removing a check, and when done, the block of code should look like this:
for (i = 0; i < ARRAY_COUNT(sHoldEffectToType); i++)
{
if (attackerHoldEffect == sHoldEffectToType[i][0]
&& type == sHoldEffectToType[i][1])
{
attack = (attack * (attackerHoldEffectParam + 100)) / 100;
spAttack = (spAttack * (attackerHoldEffectParam + 100)) / 100;
break;
}
}
We also need to make a similar change to the boosts that weather provides. In Gen 3, all types that can be boosted by weather are special types, so the code only checks for the boost if a special move is being used. All of the weather checks are conveniently bunched together, so we can just cut this section and paste it outside of the special check block:
if (WEATHER_HAS_EFFECT2)
{
if (gBattleWeather & WEATHER_RAIN_TEMPORARY)
{
switch (type)
{
case TYPE_FIRE:
damage /= 2;
break;
case TYPE_WATER:
damage = (15 * damage) / 10;
break;
}
}
// any weather except sun weakens solar beam
if ((gBattleWeather & (WEATHER_RAIN_ANY | WEATHER_SANDSTORM_ANY | WEATHER_HAIL_ANY)) && gCurrentMove == MOVE_SOLAR_BEAM)
damage /= 2;
// sunny
if (gBattleWeather & WEATHER_SUN_ANY)
{
switch (type)
{
case TYPE_FIRE:
damage = (15 * damage) / 10;
break;
case TYPE_WATER:
damage /= 2;
break;
}
}
The special check block ends just before the line that says return damage + 2;
, so use that as a reference if you're lost in the if
blocks. Another detail; if you want to be efficient, you can leave the Solar Beam code in the special block since it will only ever be special, but moving it out of the block does not cause it to stop working--instead, it will just be checked for every move, instead of just for special moves.
Just like weather, since Flash Fire only affects Fire-type moves, which are all special in Gen 3, the check for Flash Fire only occurs in the special block. It's actually located right after the weather checks, so you can cut and paste this check outside of the block along with them:
if ((gBattleResources->flags->flags[battlerIdAtk] & RESOURCE_FLAG_FLASH_FIRE) && type == TYPE_FIRE)
damage = (15 * damage) / 10;
Similarly to Flash Fire, Thick Fat only affects Fire and Ice moves, which are all special. For that reason, the damage calculation divides the special attack of the attacking Pokemon in half to reduce the damage taken. We will need to change this to reduce the base power of the move instead, so that it can apply to any physicality or move of the affected types: (src/pokemon.c)
if (defender->ability == ABILITY_THICK_FAT && (type == TYPE_FIRE || type == TYPE_ICE))
gBattleMovePower /= 2;
The next thing we need to do is change the arguments passed to IS_TYPE_PHYSICAL and IS_TYPE_SPECIAL. Originally, they were passed a type argument, but now we want to pass a move argument. There is one instance of each of these functions in src/pokemon.c, three more in src/battle_script_commands.c, and two more in src/battle_tv.c. The ones in pokemon.c should be changed from IS_TYPE_PHYSICAL(type) and IS_TYPE_SPECIAL(type) to IS_TYPE_PHYSICAL(gBattleMoves[move]) and IS_TYPE_SPECIAL(gBattleMoves[move]). The first one in battle_script_commands.c can also be changed like this, but the argument names are different for the second and third one. The second one looks like this originally:
IS_TYPE_PHYSICAL(moveType)
We will change this to:
IS_TYPE_PHYSICAL(gBattleMoves[gCurrentMove])
The third one also needs to be changed; originally it reads:
!IS_TYPE_PHYSICAL(moveType)
We will change this to:
IS_TYPE_SPECIAL(gBattleMoves[gCurrentMove])
The two in battle_tv.c can be changed to
IS_TYPE_PHYSICAL(move)
and
IS_TYPE_SPECIAL(move)
Note that we have changed !IS_TYPE_PHYSICAL to IS_TYPE_SPECIAL; "not physical" no longer automatically means "special" due to the introduction of the "other/status" option.
The last step is definitely the most tedious, but it is very simple. We need to go through every move and define whether it is physical, special, or other. The file that defines all move effects is located at src/data/battle_moves.h. There are plenty of resources to find out which one a move is if you do not already know. After adding these, the split is implemented. For reference, here is one example of each possible value of the physicality byte for my names and values:
{ // MOVE_ICE_PUNCH
.effect = EFFECT_FREEZE_HIT,
.power = 75,
.type = TYPE_ICE,
.accuracy = 100,
.pp = 15,
.secondaryEffectChance = 10,
.target = MOVE_TARGET_SELECTED,
.priority = 0,
.flags = FLAG_MAKES_CONTACT | FLAG_PROTECT_AFFECTED | FLAG_MIRROR_MOVE_AFFECTED,
.physicality = MOVE_PHYSICALITY_PHYSICAL,
},
{ // MOVE_GUST
.effect = EFFECT_GUST,
.power = 40,
.type = TYPE_FLYING,
.accuracy = 100,
.pp = 35,
.secondaryEffectChance = 0,
.target = MOVE_TARGET_SELECTED,
.priority = 0,
.flags = FLAG_PROTECT_AFFECTED | FLAG_MIRROR_MOVE_AFFECTED | FLAG_KINGSROCK_AFFECTED,
.physicality = MOVE_PHYSICALITY_SPECIAL,
},
{ // MOVE_SAND_ATTACK
.effect = EFFECT_ACCURACY_DOWN,
.power = 0,
.type = TYPE_GROUND,
.accuracy = 100,
.pp = 15,
.secondaryEffectChance = 0,
.target = MOVE_TARGET_SELECTED,
.priority = 0,
.flags = FLAG_PROTECT_AFFECTED | FLAG_MAGICCOAT_AFFECTED | FLAG_MIRROR_MOVE_AFFECTED,
.physicality = MOVE_PHYSICALITY_OTHER,
},
The last thing that should be noted is how AI Scripts relating to physical/special moves work in these games. The file data/battle_ai_scripts.s contains the code for the battle AI and how it chooses which moves to use. The way it does this is not by calling the macro that the damage calculation code uses; instead, it has multiple internal tables used to keep track of which types are physical and which are special. Note that this does not affect any actual damage calculation, and its effect on the AI is limited in the first place. The three things affected are:
- The likelihood of the AI using an attack-lowering vs. a special attack-lowering move
- The likelihood of the AI using Reflect vs. Light Screen
- The likelihood of the AI using Counter vs. Mirror Coat
An important note is that these decisions are all made based on the types of the opposing Pokemon itself rather than the moves it has, so the original check is pretty bad to begin with--in fact, it's arguable whether you would even notice the difference. Correcting the AI to check for the physicality of moves instead of the types of Pokemon is beyond the scope of this tutorial and is more suited to a general AI overhaul tutorial, but I wanted to mention it here to be totally clear about what's happening with these calculations.