Refactor damage formula to match Gen5+ (#3196)

* [battle, damage] refactor damage formula to match gen5+

* [test] use exact values for dry skin, swarm tests

* fixup: assume stats for dry-skin, swarm tests

---------

Co-authored-by: sbird <sbird@no.tld>
This commit is contained in:
Philipp AUER 2023-08-11 22:28:38 +02:00 committed by GitHub
parent fc66a8c476
commit 59da940283
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 680 additions and 324 deletions

View File

@ -209,14 +209,12 @@ void BufferStatChange(u8 battlerId, u8 statId, u8 stringId);
bool32 BlocksPrankster(u16 move, u8 battlerPrankster, u8 battlerDef, bool32 checkTarget); bool32 BlocksPrankster(u16 move, u8 battlerPrankster, u8 battlerDef, bool32 checkTarget);
u16 GetUsedHeldItem(u8 battler); u16 GetUsedHeldItem(u8 battler);
bool32 IsBattlerWeatherAffected(u8 battlerId, u32 weatherFlags); bool32 IsBattlerWeatherAffected(u8 battlerId, u32 weatherFlags);
u32 ApplyWeatherDamageMultiplier(u8 battlerAtk, u16 move, u8 moveType, u32 dmg, u16 holdEffectAtk, u16 holdEffectDef);
u32 GetBattlerMoveTargetType(u8 battlerId, u16 move); u32 GetBattlerMoveTargetType(u8 battlerId, u16 move);
bool32 CanTargetBattler(u8 battlerAtk, u8 battlerDef, u16 move); bool32 CanTargetBattler(u8 battlerAtk, u8 battlerDef, u16 move);
bool8 IsMoveAffectedByParentalBond(u16 move, u8 battlerId); bool8 IsMoveAffectedByParentalBond(u16 move, u8 battlerId);
void CopyMonLevelAndBaseStatsToBattleMon(u32 battler, struct Pokemon *mon); void CopyMonLevelAndBaseStatsToBattleMon(u32 battler, struct Pokemon *mon);
void CopyMonAbilityAndTypesToBattleMon(u32 battler, struct Pokemon *mon); void CopyMonAbilityAndTypesToBattleMon(u32 battler, struct Pokemon *mon);
void RecalcBattlerStats(u32 battler, struct Pokemon *mon); void RecalcBattlerStats(u32 battler, struct Pokemon *mon);
void MulModifier(u16 *modifier, u16 val);
bool32 IsAlly(u32 battlerAtk, u32 battlerDef); bool32 IsAlly(u32 battlerAtk, u32 battlerDef);
// Ability checks // Ability checks
@ -245,9 +243,4 @@ u8 GetBattlerGender(u8 battlerId);
bool8 AreBattlersOfOppositeGender(u8 battler1, u8 battler2); bool8 AreBattlersOfOppositeGender(u8 battler1, u8 battler2);
u32 CalcSecondaryEffectChance(u8 battlerId, u8 secondaryEffectChance); u32 CalcSecondaryEffectChance(u8 battlerId, u8 secondaryEffectChance);
static inline u32 ApplyModifier(uq4_12_t modifier, u32 val)
{
return UQ_4_12_TO_INT((modifier * val) + UQ_4_12_ROUND);
}
#endif // GUARD_BATTLE_UTIL_H #endif // GUARD_BATTLE_UTIL_H

View File

@ -52,10 +52,30 @@ static inline uq4_12_t uq4_12_multiply(uq4_12_t a, uq4_12_t b)
return (product + UQ_4_12_ROUND) >> UQ_4_12_SHIFT; return (product + UQ_4_12_ROUND) >> UQ_4_12_SHIFT;
} }
static inline uq4_12_t uq4_12_multiply_half_down(uq4_12_t a, uq4_12_t b)
{
u32 product = (u32) a * b;
return (product + UQ_4_12_ROUND - 1) >> UQ_4_12_SHIFT;
}
static inline uq4_12_t uq4_12_divide(uq4_12_t dividend, uq4_12_t divisor) static inline uq4_12_t uq4_12_divide(uq4_12_t dividend, uq4_12_t divisor)
{ {
if (divisor == UQ_4_12(0.0)) return UQ_4_12(0); if (divisor == UQ_4_12(0.0)) return UQ_4_12(0);
return (dividend << UQ_4_12_SHIFT) / divisor; return (dividend << UQ_4_12_SHIFT) / divisor;
} }
// Multiplies value by the UQ_4_12 number modifier.
// Returns an integer, rounded to nearest (rounding down on n.5)
static inline u32 uq4_12_multiply_by_int_half_down(uq4_12_t modifier, u32 value)
{
return UQ_4_12_TO_INT((modifier * value) + UQ_4_12_ROUND - 1);
}
// Multiplies value by the UQ_4_12 number modifier.
// Returns an integer, rounded to nearest (rounding up on n.5)
static inline u32 uq4_12_multiply_by_int_half_up(uq4_12_t modifier, u32 value)
{
return UQ_4_12_TO_INT((modifier * value) + UQ_4_12_ROUND);
}
#endif // FPMATH_H_ #endif // FPMATH_H_

View File

@ -1414,7 +1414,7 @@ static void Cmd_attackcanceler(void)
return; return;
} }
// Z-moves and Max Moves bypass protection, but deal reduced damage (factored in CalcFinalDmg) // Z-moves and Max Moves bypass protection, but deal reduced damage (factored in AccumulateOtherModifiers)
if (gBattleStruct->zmove.active && IS_BATTLER_PROTECTED(gBattlerTarget)) if (gBattleStruct->zmove.active && IS_BATTLER_PROTECTED(gBattlerTarget))
{ {
BattleScriptPush(cmd->nextInstr); BattleScriptPush(cmd->nextInstr);

View File

@ -8694,6 +8694,85 @@ static u32 CalcMoveBasePowerAfterModifiers(u16 move, u8 battlerAtk, u8 battlerDe
u16 atkAbility = GetBattlerAbility(battlerAtk); u16 atkAbility = GetBattlerAbility(battlerAtk);
u16 defAbility = GetBattlerAbility(battlerDef); u16 defAbility = GetBattlerAbility(battlerDef);
// move effect
switch (gBattleMoves[move].effect)
{
case EFFECT_FACADE:
if (gBattleMons[battlerAtk].status1 & (STATUS1_BURN | STATUS1_PSN_ANY | STATUS1_PARALYSIS | STATUS1_FROSTBITE))
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break;
case EFFECT_BRINE:
if (gBattleMons[battlerDef].hp <= (gBattleMons[battlerDef].maxHP / 2))
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break;
case EFFECT_BARB_BARRAGE:
case EFFECT_VENOSHOCK:
if (gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY)
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break;
case EFFECT_RETALIATE:
if (gSideTimers[atkSide].retaliateTimer == 1)
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break;
case EFFECT_SOLAR_BEAM:
if (IsBattlerWeatherAffected(battlerAtk, (B_WEATHER_HAIL | B_WEATHER_SANDSTORM | B_WEATHER_RAIN | B_WEATHER_SNOW)))
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5));
break;
case EFFECT_STOMPING_TANTRUM:
if (gBattleStruct->lastMoveFailed & gBitTable[battlerAtk])
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break;
case EFFECT_BULLDOZE:
case EFFECT_MAGNITUDE:
case EFFECT_EARTHQUAKE:
if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && !(gStatuses3[battlerDef] & STATUS3_SEMI_INVULNERABLE))
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5));
break;
case EFFECT_KNOCK_OFF:
#if B_KNOCK_OFF_DMG >= GEN_6
if (gBattleMons[battlerDef].item != ITEM_NONE
&& CanBattlerGetOrLoseItem(battlerDef, gBattleMons[battlerDef].item))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
#endif
break;
}
#if B_TERRAIN_TYPE_BOOST >= GEN_8
#define TERRAIN_TYPE_BOOST UQ_4_12(1.3)
#else
#define TERRAIN_TYPE_BOOST UQ_4_12(1.5)
#endif
// various effects
if (gProtectStructs[battlerAtk].helpingHand)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
if (gSpecialStatuses[battlerAtk].gemBoost)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.0) + sPercentToModifier[gSpecialStatuses[battlerAtk].gemParam]);
if (gStatuses3[battlerAtk] & STATUS3_CHARGED_UP && moveType == TYPE_ELECTRIC)
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
if (gStatuses3[battlerAtk] & STATUS3_ME_FIRST)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
if (IsBattlerTerrainAffected(battlerAtk, STATUS_FIELD_GRASSY_TERRAIN) && moveType == TYPE_GRASS)
modifier = uq4_12_multiply(modifier, TERRAIN_TYPE_BOOST);
if (IsBattlerTerrainAffected(battlerDef, STATUS_FIELD_MISTY_TERRAIN) && moveType == TYPE_DRAGON)
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5));
if (IsBattlerTerrainAffected(battlerAtk, STATUS_FIELD_ELECTRIC_TERRAIN) && moveType == TYPE_ELECTRIC)
modifier = uq4_12_multiply(modifier, TERRAIN_TYPE_BOOST);
if (IsBattlerTerrainAffected(battlerAtk, STATUS_FIELD_PSYCHIC_TERRAIN) && moveType == TYPE_PSYCHIC)
modifier = uq4_12_multiply(modifier, TERRAIN_TYPE_BOOST);
#if B_SPORT_TURNS >= GEN_6
if ((moveType == TYPE_ELECTRIC && gFieldStatuses & STATUS_FIELD_MUDSPORT)
|| (moveType == TYPE_FIRE && gFieldStatuses & STATUS_FIELD_WATERSPORT))
#else
if ((moveType == TYPE_ELECTRIC && AbilityBattleEffects(ABILITYEFFECT_FIELD_SPORT, 0, 0, ABILITYEFFECT_MUD_SPORT, 0))
|| (moveType == TYPE_FIRE && AbilityBattleEffects(ABILITYEFFECT_FIELD_SPORT, 0, 0, ABILITYEFFECT_WATER_SPORT, 0)))
#endif
#if B_SPORT_DMG_REDUCTION >= GEN_5
modifier = uq4_12_multiply(modifier, UQ_4_12(0.23));
#else
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5));
#endif
// attacker's abilities // attacker's abilities
switch (atkAbility) switch (atkAbility)
{ {
@ -8890,16 +8969,6 @@ static u32 CalcMoveBasePowerAfterModifiers(u16 move, u8 battlerAtk, u8 battlerDe
if (moveType == TYPE_FIRE) if (moveType == TYPE_FIRE)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.25)); modifier = uq4_12_multiply(modifier, UQ_4_12(1.25));
break; break;
case ABILITY_FLUFFY:
if (IsMoveMakingContact(move, battlerAtk))
{
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5));
if (updateFlags)
RecordAbilityBattle(battlerDef, defAbility);
}
if (moveType == TYPE_FIRE)
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break;
case ABILITY_PROTOSYNTHESIS: case ABILITY_PROTOSYNTHESIS:
{ {
u8 defHighestStat = GetHighestStatId(battlerDef); u8 defHighestStat = GetHighestStatId(battlerDef);
@ -8993,86 +9062,7 @@ static u32 CalcMoveBasePowerAfterModifiers(u16 move, u8 battlerAtk, u8 battlerDe
modifier = uq4_12_multiply(modifier, UQ_4_12(1.1)); modifier = uq4_12_multiply(modifier, UQ_4_12(1.1));
break; break;
} }
return uq4_12_multiply_by_int_half_down(modifier, basePower);
// move effect
switch (gBattleMoves[move].effect)
{
case EFFECT_FACADE:
if (gBattleMons[battlerAtk].status1 & (STATUS1_BURN | STATUS1_PSN_ANY | STATUS1_PARALYSIS | STATUS1_FROSTBITE))
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break;
case EFFECT_BRINE:
if (gBattleMons[battlerDef].hp <= (gBattleMons[battlerDef].maxHP / 2))
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break;
case EFFECT_BARB_BARRAGE:
case EFFECT_VENOSHOCK:
if (gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY)
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break;
case EFFECT_RETALIATE:
if (gSideTimers[atkSide].retaliateTimer == 1)
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break;
case EFFECT_SOLAR_BEAM:
if (IsBattlerWeatherAffected(battlerAtk, (B_WEATHER_HAIL | B_WEATHER_SANDSTORM | B_WEATHER_RAIN | B_WEATHER_SNOW)))
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5));
break;
case EFFECT_STOMPING_TANTRUM:
if (gBattleStruct->lastMoveFailed & gBitTable[battlerAtk])
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break;
case EFFECT_BULLDOZE:
case EFFECT_MAGNITUDE:
case EFFECT_EARTHQUAKE:
if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && !(gStatuses3[battlerDef] & STATUS3_SEMI_INVULNERABLE))
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5));
break;
case EFFECT_KNOCK_OFF:
#if B_KNOCK_OFF_DMG >= GEN_6
if (gBattleMons[battlerDef].item != ITEM_NONE
&& CanBattlerGetOrLoseItem(battlerDef, gBattleMons[battlerDef].item))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
#endif
break;
}
#if B_TERRAIN_TYPE_BOOST >= GEN_8
#define TERRAIN_TYPE_BOOST UQ_4_12(1.3)
#else
#define TERRAIN_TYPE_BOOST UQ_4_12(1.5)
#endif
// various effects
if (gProtectStructs[battlerAtk].helpingHand)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
if (gSpecialStatuses[battlerAtk].gemBoost)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.0) + sPercentToModifier[gSpecialStatuses[battlerAtk].gemParam]);
if (gStatuses3[battlerAtk] & STATUS3_CHARGED_UP && moveType == TYPE_ELECTRIC)
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
if (gStatuses3[battlerAtk] & STATUS3_ME_FIRST)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && moveType == TYPE_GRASS && IsBattlerGrounded(battlerAtk) && !(gStatuses3[battlerAtk] & STATUS3_SEMI_INVULNERABLE))
modifier = uq4_12_multiply(modifier, TERRAIN_TYPE_BOOST);
if (gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN && moveType == TYPE_DRAGON && IsBattlerGrounded(battlerDef) && !(gStatuses3[battlerDef] & STATUS3_SEMI_INVULNERABLE))
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5));
if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN && moveType == TYPE_ELECTRIC && IsBattlerGrounded(battlerAtk) && !(gStatuses3[battlerAtk] & STATUS3_SEMI_INVULNERABLE))
modifier = uq4_12_multiply(modifier, TERRAIN_TYPE_BOOST);
if (gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN && moveType == TYPE_PSYCHIC && IsBattlerGrounded(battlerAtk) && !(gStatuses3[battlerAtk] & STATUS3_SEMI_INVULNERABLE))
modifier = uq4_12_multiply(modifier, TERRAIN_TYPE_BOOST);
#if B_SPORT_TURNS >= GEN_6
if ((moveType == TYPE_ELECTRIC && gFieldStatuses & STATUS_FIELD_MUDSPORT)
|| (moveType == TYPE_FIRE && gFieldStatuses & STATUS_FIELD_WATERSPORT))
#else
if ((moveType == TYPE_ELECTRIC && AbilityBattleEffects(ABILITYEFFECT_FIELD_SPORT, 0, 0, ABILITYEFFECT_MUD_SPORT, 0))
|| (moveType == TYPE_FIRE && AbilityBattleEffects(ABILITYEFFECT_FIELD_SPORT, 0, 0, ABILITYEFFECT_WATER_SPORT, 0)))
#endif
#if B_SPORT_DMG_REDUCTION >= GEN_5
modifier = uq4_12_multiply(modifier, UQ_4_12(0.23));
#else
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5));
#endif
return ApplyModifier(modifier, basePower);
} }
#undef TERRAIN_TYPE_BOOST #undef TERRAIN_TYPE_BOOST
@ -9136,39 +9126,39 @@ static u32 CalcAttackStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, b
case ABILITY_HUGE_POWER: case ABILITY_HUGE_POWER:
case ABILITY_PURE_POWER: case ABILITY_PURE_POWER:
if (IS_MOVE_PHYSICAL(move)) if (IS_MOVE_PHYSICAL(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
break; break;
case ABILITY_SLOW_START: case ABILITY_SLOW_START:
if (gDisableStructs[battlerAtk].slowStartTimer != 0) if (gDisableStructs[battlerAtk].slowStartTimer != 0)
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5));
break; break;
case ABILITY_SOLAR_POWER: case ABILITY_SOLAR_POWER:
if (IS_MOVE_SPECIAL(move) && IsBattlerWeatherAffected(battlerAtk, B_WEATHER_SUN)) if (IS_MOVE_SPECIAL(move) && IsBattlerWeatherAffected(battlerAtk, B_WEATHER_SUN))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
case ABILITY_DEFEATIST: case ABILITY_DEFEATIST:
if (gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 2)) if (gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 2))
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5));
break; break;
case ABILITY_FLASH_FIRE: case ABILITY_FLASH_FIRE:
if (moveType == TYPE_FIRE && gBattleResources->flags->flags[battlerAtk] & RESOURCE_FLAG_FLASH_FIRE) if (moveType == TYPE_FIRE && gBattleResources->flags->flags[battlerAtk] & RESOURCE_FLAG_FLASH_FIRE)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
case ABILITY_SWARM: case ABILITY_SWARM:
if (moveType == TYPE_BUG && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) if (moveType == TYPE_BUG && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
case ABILITY_TORRENT: case ABILITY_TORRENT:
if (moveType == TYPE_WATER && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) if (moveType == TYPE_WATER && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
case ABILITY_BLAZE: case ABILITY_BLAZE:
if (moveType == TYPE_FIRE && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) if (moveType == TYPE_FIRE && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
case ABILITY_OVERGROW: case ABILITY_OVERGROW:
if (moveType == TYPE_GRASS && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) if (moveType == TYPE_GRASS && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
#if B_PLUS_MINUS_INTERACTION >= GEN_5 #if B_PLUS_MINUS_INTERACTION >= GEN_5
case ABILITY_PLUS: case ABILITY_PLUS:
@ -9177,34 +9167,34 @@ static u32 CalcAttackStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, b
{ {
u32 partnerAbility = GetBattlerAbility(BATTLE_PARTNER(battlerAtk)); u32 partnerAbility = GetBattlerAbility(BATTLE_PARTNER(battlerAtk));
if (partnerAbility == ABILITY_PLUS || partnerAbility == ABILITY_MINUS) if (partnerAbility == ABILITY_PLUS || partnerAbility == ABILITY_MINUS)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
} }
break; break;
#else #else
case ABILITY_PLUS: case ABILITY_PLUS:
if (IS_MOVE_SPECIAL(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && GetBattlerAbility(BATTLE_PARTNER(battlerAtk)) == ABILITY_MINUS) if (IS_MOVE_SPECIAL(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && GetBattlerAbility(BATTLE_PARTNER(battlerAtk)) == ABILITY_MINUS)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
case ABILITY_MINUS: case ABILITY_MINUS:
if (IS_MOVE_SPECIAL(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && GetBattlerAbility(BATTLE_PARTNER(battlerAtk)) == ABILITY_PLUS) if (IS_MOVE_SPECIAL(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && GetBattlerAbility(BATTLE_PARTNER(battlerAtk)) == ABILITY_PLUS)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
#endif #endif
case ABILITY_FLOWER_GIFT: case ABILITY_FLOWER_GIFT:
if (gBattleMons[battlerAtk].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(battlerAtk, B_WEATHER_SUN) && IS_MOVE_PHYSICAL(move)) if (gBattleMons[battlerAtk].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(battlerAtk, B_WEATHER_SUN) && IS_MOVE_PHYSICAL(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
case ABILITY_HUSTLE: case ABILITY_HUSTLE:
if (IS_MOVE_PHYSICAL(move)) if (IS_MOVE_PHYSICAL(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
case ABILITY_STAKEOUT: case ABILITY_STAKEOUT:
if (gDisableStructs[battlerDef].isFirstTurn == 2) // just switched in if (gDisableStructs[battlerDef].isFirstTurn == 2) // just switched in
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
break; break;
case ABILITY_GUTS: case ABILITY_GUTS:
if (gBattleMons[battlerAtk].status1 & STATUS1_ANY && IS_MOVE_PHYSICAL(move)) if (gBattleMons[battlerAtk].status1 & STATUS1_ANY && IS_MOVE_PHYSICAL(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
} }
@ -9214,15 +9204,11 @@ static u32 CalcAttackStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, b
case ABILITY_THICK_FAT: case ABILITY_THICK_FAT:
if (moveType == TYPE_FIRE || moveType == TYPE_ICE) if (moveType == TYPE_FIRE || moveType == TYPE_ICE)
{ {
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5));
if (updateFlags) if (updateFlags)
RecordAbilityBattle(battlerDef, ABILITY_THICK_FAT); RecordAbilityBattle(battlerDef, ABILITY_THICK_FAT);
} }
break; break;
case ABILITY_ICE_SCALES:
if (IS_MOVE_SPECIAL(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(0.5));
break;
} }
// ally's abilities // ally's abilities
@ -9232,7 +9218,7 @@ static u32 CalcAttackStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, b
{ {
case ABILITY_FLOWER_GIFT: case ABILITY_FLOWER_GIFT:
if (gBattleMons[BATTLE_PARTNER(battlerAtk)].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(BATTLE_PARTNER(battlerAtk), B_WEATHER_SUN) && IS_MOVE_PHYSICAL(move)) if (gBattleMons[BATTLE_PARTNER(battlerAtk)].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(BATTLE_PARTNER(battlerAtk), B_WEATHER_SUN) && IS_MOVE_PHYSICAL(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
} }
} }
@ -9242,34 +9228,34 @@ static u32 CalcAttackStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, b
{ {
case HOLD_EFFECT_THICK_CLUB: case HOLD_EFFECT_THICK_CLUB:
if ((atkBaseSpeciesId == SPECIES_CUBONE || atkBaseSpeciesId == SPECIES_MAROWAK) && IS_MOVE_PHYSICAL(move)) if ((atkBaseSpeciesId == SPECIES_CUBONE || atkBaseSpeciesId == SPECIES_MAROWAK) && IS_MOVE_PHYSICAL(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
break; break;
case HOLD_EFFECT_DEEP_SEA_TOOTH: case HOLD_EFFECT_DEEP_SEA_TOOTH:
if (gBattleMons[battlerAtk].species == SPECIES_CLAMPERL && IS_MOVE_SPECIAL(move)) if (gBattleMons[battlerAtk].species == SPECIES_CLAMPERL && IS_MOVE_SPECIAL(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
break; break;
case HOLD_EFFECT_LIGHT_BALL: case HOLD_EFFECT_LIGHT_BALL:
if (atkBaseSpeciesId == SPECIES_PIKACHU) if (atkBaseSpeciesId == SPECIES_PIKACHU)
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
break; break;
case HOLD_EFFECT_CHOICE_BAND: case HOLD_EFFECT_CHOICE_BAND:
if (IS_MOVE_PHYSICAL(move)) if (IS_MOVE_PHYSICAL(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
case HOLD_EFFECT_CHOICE_SPECS: case HOLD_EFFECT_CHOICE_SPECS:
if (IS_MOVE_SPECIAL(move)) if (IS_MOVE_SPECIAL(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
} }
// The offensive stats of a Player's Pokémon are boosted by x1.1 (+10%) if they have the 1st badge and 7th badges. // The offensive stats of a Player's Pokémon are boosted by x1.1 (+10%) if they have the 1st badge and 7th badges.
// Having the 1st badge boosts physical attack while having the 7th badge boosts special attack. // Having the 1st badge boosts physical attack while having the 7th badge boosts special attack.
if (ShouldGetStatBadgeBoost(FLAG_BADGE01_GET, battlerAtk) && IS_MOVE_PHYSICAL(move)) if (ShouldGetStatBadgeBoost(FLAG_BADGE01_GET, battlerAtk) && IS_MOVE_PHYSICAL(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.1)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1));
if (ShouldGetStatBadgeBoost(FLAG_BADGE07_GET, battlerAtk) && IS_MOVE_SPECIAL(move)) if (ShouldGetStatBadgeBoost(FLAG_BADGE07_GET, battlerAtk) && IS_MOVE_SPECIAL(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.1)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1));
return ApplyModifier(modifier, atkStat); return uq4_12_multiply_by_int_half_down(modifier, atkStat);
} }
static bool32 CanEvolve(u32 species) static bool32 CanEvolve(u32 species)
@ -9343,7 +9329,7 @@ static u32 CalcDefenseStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType,
case ABILITY_MARVEL_SCALE: case ABILITY_MARVEL_SCALE:
if (gBattleMons[battlerDef].status1 & STATUS1_ANY && usesDefStat) if (gBattleMons[battlerDef].status1 & STATUS1_ANY && usesDefStat)
{ {
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
if (updateFlags) if (updateFlags)
RecordAbilityBattle(battlerDef, ABILITY_MARVEL_SCALE); RecordAbilityBattle(battlerDef, ABILITY_MARVEL_SCALE);
} }
@ -9351,7 +9337,7 @@ static u32 CalcDefenseStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType,
case ABILITY_FUR_COAT: case ABILITY_FUR_COAT:
if (usesDefStat) if (usesDefStat)
{ {
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
if (updateFlags) if (updateFlags)
RecordAbilityBattle(battlerDef, ABILITY_FUR_COAT); RecordAbilityBattle(battlerDef, ABILITY_FUR_COAT);
} }
@ -9359,22 +9345,18 @@ static u32 CalcDefenseStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType,
case ABILITY_GRASS_PELT: case ABILITY_GRASS_PELT:
if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && usesDefStat) if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && usesDefStat)
{ {
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
if (updateFlags) if (updateFlags)
RecordAbilityBattle(battlerDef, ABILITY_GRASS_PELT); RecordAbilityBattle(battlerDef, ABILITY_GRASS_PELT);
} }
break; break;
case ABILITY_FLOWER_GIFT: case ABILITY_FLOWER_GIFT:
if (gBattleMons[battlerDef].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(battlerDef, B_WEATHER_SUN) && !usesDefStat) if (gBattleMons[battlerDef].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(battlerDef, B_WEATHER_SUN) && !usesDefStat)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break;
case ABILITY_PUNK_ROCK:
if (gBattleMoves[move].soundMove)
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break; break;
case ABILITY_PURIFYING_SALT: case ABILITY_PURIFYING_SALT:
if (gBattleMoves[move].type == TYPE_GHOST) if (gBattleMoves[move].type == TYPE_GHOST)
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
break; break;
} }
@ -9385,7 +9367,7 @@ static u32 CalcDefenseStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType,
{ {
case ABILITY_FLOWER_GIFT: case ABILITY_FLOWER_GIFT:
if (gBattleMons[BATTLE_PARTNER(battlerDef)].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(BATTLE_PARTNER(battlerDef), B_WEATHER_SUN) && !usesDefStat) if (gBattleMons[BATTLE_PARTNER(battlerDef)].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(BATTLE_PARTNER(battlerDef), B_WEATHER_SUN) && !usesDefStat)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
} }
} }
@ -9395,240 +9377,374 @@ static u32 CalcDefenseStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType,
{ {
case HOLD_EFFECT_DEEP_SEA_SCALE: case HOLD_EFFECT_DEEP_SEA_SCALE:
if (gBattleMons[battlerDef].species == SPECIES_CLAMPERL && !usesDefStat) if (gBattleMons[battlerDef].species == SPECIES_CLAMPERL && !usesDefStat)
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
break; break;
case HOLD_EFFECT_METAL_POWDER: case HOLD_EFFECT_METAL_POWDER:
if (gBattleMons[battlerDef].species == SPECIES_DITTO && usesDefStat && !(gBattleMons[battlerDef].status2 & STATUS2_TRANSFORMED)) if (gBattleMons[battlerDef].species == SPECIES_DITTO && usesDefStat && !(gBattleMons[battlerDef].status2 & STATUS2_TRANSFORMED))
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
break; break;
case HOLD_EFFECT_EVIOLITE: case HOLD_EFFECT_EVIOLITE:
if (CanEvolve(gBattleMons[battlerDef].species)) if (CanEvolve(gBattleMons[battlerDef].species))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
case HOLD_EFFECT_ASSAULT_VEST: case HOLD_EFFECT_ASSAULT_VEST:
if (!usesDefStat) if (!usesDefStat)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
#if B_SOUL_DEW_BOOST <= GEN_6 #if B_SOUL_DEW_BOOST <= GEN_6
case HOLD_EFFECT_SOUL_DEW: case HOLD_EFFECT_SOUL_DEW:
if ((gBattleMons[battlerDef].species == SPECIES_LATIAS || gBattleMons[battlerDef].species == SPECIES_LATIOS) if ((gBattleMons[battlerDef].species == SPECIES_LATIAS || gBattleMons[battlerDef].species == SPECIES_LATIOS)
&& !(gBattleTypeFlags & BATTLE_TYPE_FRONTIER) && !(gBattleTypeFlags & BATTLE_TYPE_FRONTIER)
&& !usesDefStat) && !usesDefStat)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
break; break;
#endif #endif
} }
// sandstorm sp.def boost for rock types // sandstorm sp.def boost for rock types
if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_ROCK) && gBattleWeather & B_WEATHER_SANDSTORM && WEATHER_HAS_EFFECT && !usesDefStat) if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_ROCK) && gBattleWeather & B_WEATHER_SANDSTORM && WEATHER_HAS_EFFECT && !usesDefStat)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
// snow def boost for ice types // snow def boost for ice types
if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_ICE) && gBattleWeather & B_WEATHER_SNOW && WEATHER_HAS_EFFECT && usesDefStat) if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_ICE) && gBattleWeather & B_WEATHER_SNOW && WEATHER_HAS_EFFECT && usesDefStat)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5));
// The defensive stats of a Player's Pokémon are boosted by x1.1 (+10%) if they have the 5th badge and 7th badges. // The defensive stats of a Player's Pokémon are boosted by x1.1 (+10%) if they have the 5th badge and 7th badges.
// Having the 5th badge boosts physical defense while having the 7th badge boosts special defense. // Having the 5th badge boosts physical defense while having the 7th badge boosts special defense.
if (ShouldGetStatBadgeBoost(FLAG_BADGE05_GET, battlerDef) && IS_MOVE_PHYSICAL(move)) if (ShouldGetStatBadgeBoost(FLAG_BADGE05_GET, battlerDef) && IS_MOVE_PHYSICAL(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.1)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1));
if (ShouldGetStatBadgeBoost(FLAG_BADGE07_GET, battlerDef) && IS_MOVE_SPECIAL(move)) if (ShouldGetStatBadgeBoost(FLAG_BADGE07_GET, battlerDef) && IS_MOVE_SPECIAL(move))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.1)); modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1));
return ApplyModifier(modifier, defStat); return uq4_12_multiply_by_int_half_down(modifier, defStat);
} }
static u32 CalcFinalDmg(u32 dmg, u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, uq4_12_t typeEffectivenessModifier, bool32 isCrit, bool32 updateFlags) // base damage formula before adding any modifiers
static inline s32 CalculateBaseDamage(u32 power, u32 userFinalAttack, u32 level, u32 targetFinalDefense)
{ {
u32 percentBoost; return power * userFinalAttack * (2 * level / 5 + 2) / targetFinalDefense / 50 + 2;
u32 abilityAtk = GetBattlerAbility(battlerAtk); }
u32 abilityDef = GetBattlerAbility(battlerDef);
u32 defSide = GET_BATTLER_SIDE(battlerDef);
uq4_12_t finalModifier = UQ_4_12(1.0);
u16 itemDef = gBattleMons[battlerDef].item;
u16 holdEffectAtk = GetBattlerHoldEffect(battlerAtk, TRUE);
u16 holdEffectDef = GetBattlerHoldEffect(battlerDef, TRUE);
// check multiple targets in double battle
if (GetMoveTargetCount(move, battlerAtk, battlerDef) >= 2)
#if B_MULTIPLE_TARGETS_DMG >= GEN_4 #if B_MULTIPLE_TARGETS_DMG >= GEN_4
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.75)); #define V_MULTIPLE_TARGETS_DMG UQ_4_12(0.75)
#else #else
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.5)); #define V_MULTIPLE_TARGETS_DMG UQ_4_12(0.5)
#endif #endif
// take type effectiveness
finalModifier = uq4_12_multiply(finalModifier, typeEffectivenessModifier);
// check crit
if (isCrit)
#if B_CRIT_MULTIPLIER >= GEN_6 #if B_CRIT_MULTIPLIER >= GEN_6
dmg = ApplyModifier(UQ_4_12(1.5), dmg); #define V_CRIT_MULTIPLIER UQ_4_12(1.5)
#else #else
dmg = ApplyModifier(UQ_4_12(2.0), dmg); #define V_CRIT_MULTIPLIER UQ_4_12(2.0)
#endif #endif
// check burn
if (gBattleMons[battlerAtk].status1 & STATUS1_BURN && IS_MOVE_PHYSICAL(move)
#if B_BURN_FACADE_DMG >= GEN_6 #if B_BURN_FACADE_DMG >= GEN_6
&& gBattleMoves[move].effect != EFFECT_FACADE #define FACADE_PREVENTS_BURN_MALUS(move) (gBattleMoves[move].effect == EFFECT_FACADE)
#else
#define FACADE_PREVENTS_BURN_MALUS(move) (FALSE)
#endif #endif
&& abilityAtk != ABILITY_GUTS)
dmg = ApplyModifier(UQ_4_12(0.5), dmg);
// check frostbite #if B_PARENTAL_BOND_DMG < GEN_7
if (gBattleMons[battlerAtk].status1 & STATUS1_FROSTBITE && IS_MOVE_SPECIAL(move) #define V_PARENTAL_BOND_DMG UQ_4_12(0.5)
#if B_BURN_FACADE_DMG >= GEN_6 #else
&& gBattleMoves[move].effect != EFFECT_FACADE #define V_PARENTAL_BOND_DMG UQ_4_12(0.25)
#endif #endif
static inline uq4_12_t GetTargetDamageModifier(u32 move, u32 battlerAtk, u32 battlerDef)
{
if (GetMoveTargetCount(move, battlerAtk, battlerDef) >= 2)
return V_MULTIPLE_TARGETS_DMG;
return UQ_4_12(1.0);
}
static inline uq4_12_t GetParentalBondModifier(u32 battlerAtk)
{
if (gSpecialStatuses[battlerAtk].parentalBondState != PARENTAL_BOND_2ND_HIT)
return UQ_4_12(1.0);
return V_PARENTAL_BOND_DMG;
}
static inline uq4_12_t GetSameTypeAttackBonusModifier(u32 battlerAtk, u32 moveType, u32 move, u32 abilityAtk)
{
if (!IS_BATTLER_OF_TYPE(battlerAtk, moveType) || move == MOVE_STRUGGLE || move == MOVE_NONE)
return UQ_4_12(1.0);
return (abilityAtk == ABILITY_ADAPTABILITY) ? UQ_4_12(2.0) : UQ_4_12(1.5);
}
// Utility Umbrella holders take normal damage from what would be rain- and sun-weakened attacks.
static uq4_12_t GetWeatherDamageModifier(u32 battlerAtk, u32 move, u32 moveType, u32 holdEffectAtk, u32 holdEffectDef)
{
if (!WEATHER_HAS_EFFECT)
return UQ_4_12(1.0);
if (gBattleMoves[move].effect == EFFECT_HYDRO_STEAM && (gBattleWeather & B_WEATHER_SUN) && holdEffectAtk != HOLD_EFFECT_UTILITY_UMBRELLA)
return UQ_4_12(1.5);
if (holdEffectDef == HOLD_EFFECT_UTILITY_UMBRELLA)
return UQ_4_12(1.0);
if (gBattleWeather & B_WEATHER_RAIN)
{
if (moveType != TYPE_FIRE && moveType != TYPE_WATER)
return UQ_4_12(1.0);
return (moveType == TYPE_FIRE) ? UQ_4_12(0.5) : UQ_4_12(1.5);
}
if (gBattleWeather & B_WEATHER_SUN)
{
if (moveType != TYPE_FIRE && moveType != TYPE_WATER)
return UQ_4_12(1.0);
return (moveType == TYPE_WATER) ? UQ_4_12(0.5) : UQ_4_12(1.5);
}
return UQ_4_12(1.0);
}
static inline uq4_12_t GetBurnOrFrostBiteModifier(u32 battlerAtk, u32 move, u32 abilityAtk)
{
if (gBattleMons[battlerAtk].status1 & STATUS1_BURN
&& IS_MOVE_PHYSICAL(move)
&& !FACADE_PREVENTS_BURN_MALUS(move)
&& abilityAtk != ABILITY_GUTS) && abilityAtk != ABILITY_GUTS)
dmg = ApplyModifier(UQ_4_12(0.5), dmg); return UQ_4_12(0.5);
if (gBattleMons[battlerAtk].status1 & STATUS1_FROSTBITE
// check weather && IS_MOVE_SPECIAL(move)
dmg = ApplyWeatherDamageMultiplier(battlerAtk, move, moveType, dmg, holdEffectAtk, holdEffectDef); && !FACADE_PREVENTS_BURN_MALUS(move)
&& abilityAtk != ABILITY_GUTS)
// check stab return UQ_4_12(0.5);
if (IS_BATTLER_OF_TYPE(battlerAtk, moveType) && move != MOVE_STRUGGLE && move != MOVE_NONE) return UQ_4_12(1.0);
{
if (abilityAtk == ABILITY_ADAPTABILITY)
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(2.0));
else
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(1.5));
} }
// Collision Course, Electro Drift static inline uq4_12_t GetCriticalModifier(bool32 isCrit)
if (gBattleMoves[move].effect == EFFECT_COLLISION_COURSE && typeEffectivenessModifier >= UQ_4_12(2.0))
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(1.3333));
// reflect, light screen, aurora veil
if (((gSideStatuses[defSide] & SIDE_STATUS_REFLECT && IS_MOVE_PHYSICAL(move))
|| (gSideStatuses[defSide] & SIDE_STATUS_LIGHTSCREEN && IS_MOVE_SPECIAL(move))
|| (gSideStatuses[defSide] & SIDE_STATUS_AURORA_VEIL))
&& abilityAtk != ABILITY_INFILTRATOR
&& !(isCrit)
&& !gProtectStructs[battlerAtk].confusionSelfDmg)
{ {
if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) return isCrit ? V_CRIT_MULTIPLIER : UQ_4_12(1.0);
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.66));
else
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.5));
} }
// Parental Bond Second Strike static inline uq4_12_t GetZMoveAgainstProtectionModifier(u32 battlerDef)
if (gSpecialStatuses[battlerAtk].parentalBondState == PARENTAL_BOND_2ND_HIT)
{ {
if (B_PARENTAL_BOND_DMG < GEN_7)
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.5));
else
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.25));
}
// Z-Moves and Max Moves bypass Protect and do 25% of their original damage
if (gBattleStruct->zmove.active && IS_BATTLER_PROTECTED(battlerDef)) if (gBattleStruct->zmove.active && IS_BATTLER_PROTECTED(battlerDef))
{ return UQ_4_12(0.25);
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.25)); return UQ_4_12(1.0);
} }
// attacker's abilities static inline uq4_12_t GetMinimizeModifier(u32 move, u32 battlerDef)
{
if (gBattleMoves[move].minimizeDoubleDamage && gStatuses3[battlerDef] & STATUS3_MINIMIZED)
return UQ_4_12(2.0);
return UQ_4_12(1.0);
}
static inline uq4_12_t GetUndergroundModifier(u32 move, u32 battlerDef)
{
if (gBattleMoves[move].damagesUnderground && gStatuses3[battlerDef] & STATUS3_UNDERGROUND)
return UQ_4_12(2.0);
return UQ_4_12(1.0);
}
static inline uq4_12_t GetDiveModifier(u32 move, u32 battlerDef)
{
if (gBattleMoves[move].damagesUnderwater && gStatuses3[battlerDef] & STATUS3_UNDERWATER)
return UQ_4_12(2.0);
return UQ_4_12(1.0);
}
static inline uq4_12_t GetAirborneModifier(u32 move, u32 battlerDef)
{
if (gBattleMoves[move].damagesAirborneDoubleDamage && gStatuses3[battlerDef] & STATUS3_ON_AIR)
return UQ_4_12(2.0);
return UQ_4_12(1.0);
}
static inline uq4_12_t GetScreensModifier(u32 move, u32 battlerAtk, u32 battlerDef, bool32 isCrit)
{
u32 sideStatus = gSideStatuses[GET_BATTLER_SIDE(battlerDef)];
bool32 lightScreen = (sideStatus & SIDE_STATUS_LIGHTSCREEN) && IS_MOVE_SPECIAL(move);
bool32 reflect = (sideStatus & SIDE_STATUS_REFLECT) && IS_MOVE_PHYSICAL(move);
bool32 auroraVeil = sideStatus & SIDE_STATUS_AURORA_VEIL;
u32 abilityAtk = GetBattlerAbility(battlerAtk);
if (isCrit || abilityAtk == ABILITY_INFILTRATOR || gProtectStructs[battlerAtk].confusionSelfDmg)
return UQ_4_12(1.0);
if (reflect || lightScreen || auroraVeil)
return (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) ? UQ_4_12(0.667) : UQ_4_12(0.5);
return UQ_4_12(1.0);
}
static inline uq4_12_t GetCollisionCourseElectroDriftModifier(u32 move, uq4_12_t typeEffectivenessModifier)
{
if (gBattleMoves[move].effect == EFFECT_COLLISION_COURSE && typeEffectivenessModifier >= UQ_4_12(2.0))
return UQ_4_12(1.3333);
return UQ_4_12(1.0);
}
static inline uq4_12_t GetAttackerAbilitiesModifier(u32 battlerAtk, uq4_12_t typeEffectivenessModifier, bool32 isCrit)
{
u32 abilityAtk = GetBattlerAbility(battlerAtk);
switch (abilityAtk) switch (abilityAtk)
{ {
case ABILITY_TINTED_LENS: case ABILITY_NEUROFORCE:
if (typeEffectivenessModifier <= UQ_4_12(0.5)) if (typeEffectivenessModifier >= UQ_4_12(2.0))
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(2.0)); return UQ_4_12(1.25);
break; break;
case ABILITY_SNIPER: case ABILITY_SNIPER:
if (isCrit) if (isCrit)
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(1.5)); return UQ_4_12(1.5);
break; break;
case ABILITY_NEUROFORCE: case ABILITY_TINTED_LENS:
if (typeEffectivenessModifier >= UQ_4_12(2.0)) if (typeEffectivenessModifier <= UQ_4_12(0.5))
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(1.25)); return UQ_4_12(2.0);
break; break;
} }
return UQ_4_12(1.0);
}
// target's abilities static inline uq4_12_t GetDefenderAbilitiesModifier(u32 move, u32 moveType, u32 battlerAtk, u32 battlerDef, uq4_12_t typeEffectivenessModifier)
{
u32 abilityDef = GetBattlerAbility(battlerDef);
switch (abilityDef) switch (abilityDef)
{ {
case ABILITY_MULTISCALE: case ABILITY_MULTISCALE:
case ABILITY_SHADOW_SHIELD: case ABILITY_SHADOW_SHIELD:
if (BATTLER_MAX_HP(battlerDef)) if (BATTLER_MAX_HP(battlerDef))
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.5)); return UQ_4_12(0.5);
break; break;
case ABILITY_FILTER: case ABILITY_FILTER:
case ABILITY_SOLID_ROCK: case ABILITY_SOLID_ROCK:
case ABILITY_PRISM_ARMOR: case ABILITY_PRISM_ARMOR:
if (typeEffectivenessModifier >= UQ_4_12(2.0)) if (typeEffectivenessModifier >= UQ_4_12(2.0))
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.75)); return UQ_4_12(0.75);
break;
case ABILITY_FLUFFY:
if (!IsMoveMakingContact(move, battlerAtk) && moveType == TYPE_FIRE)
return UQ_4_12(2.0);
if (IsMoveMakingContact(move, battlerAtk) && moveType != TYPE_FIRE)
return UQ_4_12(0.5);
break;
case ABILITY_PUNK_ROCK:
if (gBattleMoves[move].soundMove)
return UQ_4_12(0.5);
break;
case ABILITY_ICE_SCALES:
if (IS_MOVE_SPECIAL(move))
return UQ_4_12(0.5);
break; break;
} }
return UQ_4_12(1.0);
}
// target's ally's abilities static inline uq4_12_t GetDefenderPartnerAbilitiesModifier(u32 battlerPartnerDef)
if (IsBattlerAlive(BATTLE_PARTNER(battlerDef)))
{ {
switch (GetBattlerAbility(BATTLE_PARTNER(battlerDef))) if (!IsBattlerAlive(battlerPartnerDef))
return UQ_4_12(1.0);
switch (GetBattlerAbility(battlerPartnerDef))
{ {
case ABILITY_FRIEND_GUARD: case ABILITY_FRIEND_GUARD:
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.75)); return UQ_4_12(0.75);
break; break;
} }
return UQ_4_12(1.0);
} }
// attacker's hold effect static inline uq4_12_t GetAttackerItemsModifier(u32 battlerAtk, uq4_12_t typeEffectivenessModifier)
{
u32 holdEffectAtk = GetBattlerHoldEffect(battlerAtk, TRUE);
u32 percentBoost;
switch (holdEffectAtk) switch (holdEffectAtk)
{ {
case HOLD_EFFECT_METRONOME: case HOLD_EFFECT_METRONOME:
percentBoost = min((gBattleStruct->sameMoveTurns[battlerAtk] * GetBattlerHoldEffectParam(battlerAtk)), 100); percentBoost = min((gBattleStruct->sameMoveTurns[battlerAtk] * GetBattlerHoldEffectParam(battlerAtk)), 100);
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(1.0) + sPercentToModifier[percentBoost]); return sPercentToModifier[percentBoost];
break; break;
case HOLD_EFFECT_EXPERT_BELT: case HOLD_EFFECT_EXPERT_BELT:
if (typeEffectivenessModifier >= UQ_4_12(2.0)) if (typeEffectivenessModifier >= UQ_4_12(2.0))
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(1.2)); return UQ_4_12(1.2);
break; break;
case HOLD_EFFECT_LIFE_ORB: case HOLD_EFFECT_LIFE_ORB:
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(1.3)); return UQ_4_12(1.3);
break; break;
} }
return UQ_4_12(1.0);
}
static inline uq4_12_t GetDefenderItemsModifier(u32 moveType, u32 battlerDef, uq4_12_t typeEffectivenessModifier, bool32 updateFlags)
{
u32 holdEffectDef = GetBattlerHoldEffect(battlerDef, TRUE);
u32 holdEffectDefParam = GetBattlerHoldEffectParam(battlerDef);
u32 itemDef = gBattleMons[battlerDef].item;
u32 abilityDef = GetBattlerAbility(battlerDef);
// target's hold effect
switch (holdEffectDef) switch (holdEffectDef)
{ {
// berries reducing dmg
case HOLD_EFFECT_RESIST_BERRY: case HOLD_EFFECT_RESIST_BERRY:
if (moveType == GetBattlerHoldEffectParam(battlerDef) if (UnnerveOn(battlerDef, itemDef))
&& (moveType == TYPE_NORMAL || typeEffectivenessModifier >= UQ_4_12(2.0)) return UQ_4_12(1.0);
&& !UnnerveOn(battlerDef, itemDef)) if (moveType == holdEffectDefParam && (moveType == TYPE_NORMAL || typeEffectivenessModifier >= UQ_4_12(2.0)))
{ {
if (abilityDef == ABILITY_RIPEN)
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.25));
else
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(0.5));
if (updateFlags) if (updateFlags)
gSpecialStatuses[battlerDef].berryReduced = TRUE; gSpecialStatuses[battlerDef].berryReduced = TRUE;
return (abilityDef == ABILITY_RIPEN) ? UQ_4_12(0.25) : UQ_4_12(0.5);
} }
break; break;
} }
return UQ_4_12(1.0);
if (gBattleMoves[move].minimizeDoubleDamage && gStatuses3[battlerDef] & STATUS3_MINIMIZED)
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(2.0));
if (gBattleMoves[move].damagesUnderground && gStatuses3[battlerDef] & STATUS3_UNDERGROUND)
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(2.0));
if (gBattleMoves[move].damagesUnderwater && gStatuses3[battlerDef] & STATUS3_UNDERWATER)
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(2.0));
if (gBattleMoves[move].damagesAirborneDoubleDamage && gStatuses3[battlerDef] & STATUS3_ON_AIR)
finalModifier = uq4_12_multiply(finalModifier, UQ_4_12(2.0));
dmg = ApplyModifier(finalModifier, dmg);
if (dmg == 0)
dmg = 1;
return dmg;
} }
static s32 DoMoveDamageCalc(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, s32 fixedBasePower, #define DAMAGE_MULTIPLY_MODIFIER(modifier) do { \
finalModifier = uq4_12_multiply_half_down(modifier, finalModifier); \
} while (0)
// Calculates the "other" modifier which accounts for held items, abilities,
// or very specific interactions of moves that are not handled in the basic
// damage calculation. It is implemented as described by bulbapedia:
// https://bulbapedia.bulbagarden.net/wiki/Damage#Generation_V_onward
// Please Note: Fixed Point Multiplication is not associative.
// The order of operations is relevant.
static uq4_12_t GetOtherModifiers(u32 move, u32 moveType, u32 battlerAtk, u32 battlerDef, bool32 isCrit, uq4_12_t typeEffectivenessModifier, bool32 updateFlags)
{
u32 abilityAtk = GetBattlerAbility(battlerAtk);
uq4_12_t finalModifier = UQ_4_12(1.0);
u32 battlerDefPartner = BATTLE_PARTNER(battlerDef);
u32 unmodifiedAttackerSpeed = gBattleMons[battlerAtk].speed;
u32 unmodifiedDefenderSpeed = gBattleMons[battlerDef].speed;
//TODO: Behemoth Blade, Behemoth Bash, Dynamax Cannon (Dynamax)
DAMAGE_MULTIPLY_MODIFIER(GetMinimizeModifier(move, battlerDef));
DAMAGE_MULTIPLY_MODIFIER(GetUndergroundModifier(move, battlerDef));
DAMAGE_MULTIPLY_MODIFIER(GetDiveModifier(move, battlerDef));
DAMAGE_MULTIPLY_MODIFIER(GetAirborneModifier(move, battlerDef));
DAMAGE_MULTIPLY_MODIFIER(GetScreensModifier(move, battlerAtk, battlerDef, isCrit));
DAMAGE_MULTIPLY_MODIFIER(GetCollisionCourseElectroDriftModifier(move, typeEffectivenessModifier));
if (unmodifiedAttackerSpeed >= unmodifiedDefenderSpeed)
{
DAMAGE_MULTIPLY_MODIFIER(GetAttackerAbilitiesModifier(battlerAtk, typeEffectivenessModifier, isCrit));
DAMAGE_MULTIPLY_MODIFIER(GetDefenderAbilitiesModifier(move, moveType, battlerAtk, battlerDef, typeEffectivenessModifier));
DAMAGE_MULTIPLY_MODIFIER(GetDefenderPartnerAbilitiesModifier(battlerDefPartner));
DAMAGE_MULTIPLY_MODIFIER(GetAttackerItemsModifier(battlerAtk, typeEffectivenessModifier));
DAMAGE_MULTIPLY_MODIFIER(GetDefenderItemsModifier(moveType, battlerDef, typeEffectivenessModifier, updateFlags));
}
else
{
DAMAGE_MULTIPLY_MODIFIER(GetDefenderAbilitiesModifier(move, moveType, battlerAtk, battlerDef, typeEffectivenessModifier));
DAMAGE_MULTIPLY_MODIFIER(GetDefenderPartnerAbilitiesModifier(battlerDefPartner));
DAMAGE_MULTIPLY_MODIFIER(GetAttackerAbilitiesModifier(battlerAtk, typeEffectivenessModifier, isCrit));
DAMAGE_MULTIPLY_MODIFIER(GetDefenderItemsModifier(moveType, battlerDef, typeEffectivenessModifier, updateFlags));
DAMAGE_MULTIPLY_MODIFIER(GetAttackerItemsModifier(battlerAtk, typeEffectivenessModifier));
}
return finalModifier;
}
#undef DAMAGE_ACCUMULATE_MULTIPLIER
#define DAMAGE_APPLY_MODIFIER(modifier) do { \
dmg = uq4_12_multiply_by_int_half_down(modifier, dmg); \
} while (0)
static s32 DoMoveDamageCalc(u32 move, u32 battlerAtk, u32 battlerDef, u32 moveType, s32 fixedBasePower,
bool32 isCrit, bool32 randomFactor, bool32 updateFlags, uq4_12_t typeEffectivenessModifier) bool32 isCrit, bool32 randomFactor, bool32 updateFlags, uq4_12_t typeEffectivenessModifier)
{ {
s32 dmg; s32 dmg;
u32 userFinalAttack;
u32 targetFinalDefense;
u32 holdEffectAtk = GetBattlerHoldEffect(battlerAtk, TRUE);
u32 holdEffectDef = GetBattlerHoldEffect(battlerDef, TRUE);
u32 abilityAtk = GetBattlerAbility(battlerAtk);
// Don't calculate damage if the move has no effect on target. if (typeEffectivenessModifier == UQ_4_12(0.0))
if (typeEffectivenessModifier == UQ_4_12(0))
return 0; return 0;
if (fixedBasePower) if (fixedBasePower)
@ -9636,29 +9752,34 @@ static s32 DoMoveDamageCalc(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType,
else else
gBattleMovePower = CalcMoveBasePowerAfterModifiers(move, battlerAtk, battlerDef, moveType, updateFlags); gBattleMovePower = CalcMoveBasePowerAfterModifiers(move, battlerAtk, battlerDef, moveType, updateFlags);
// long dmg basic formula userFinalAttack = CalcAttackStat(move, battlerAtk, battlerDef, moveType, isCrit, updateFlags);
dmg = ((gBattleMons[battlerAtk].level * 2) / 5) + 2; targetFinalDefense = CalcDefenseStat(move, battlerAtk, battlerDef, moveType, isCrit, updateFlags);
dmg *= gBattleMovePower;
dmg *= CalcAttackStat(move, battlerAtk, battlerDef, moveType, isCrit, updateFlags);
dmg /= CalcDefenseStat(move, battlerAtk, battlerDef, moveType, isCrit, updateFlags);
dmg = (dmg / 50) + 2;
// Calculate final modifiers. dmg = CalculateBaseDamage(gBattleMovePower, userFinalAttack, gBattleMons[battlerAtk].level, targetFinalDefense);
dmg = CalcFinalDmg(dmg, move, battlerAtk, battlerDef, moveType, typeEffectivenessModifier, isCrit, updateFlags); DAMAGE_APPLY_MODIFIER(GetTargetDamageModifier(move, battlerAtk, battlerDef));
DAMAGE_APPLY_MODIFIER(GetParentalBondModifier(battlerAtk));
// Add a random factor. DAMAGE_APPLY_MODIFIER(GetWeatherDamageModifier(battlerAtk, move, moveType, holdEffectAtk, holdEffectDef));
DAMAGE_APPLY_MODIFIER(GetCriticalModifier(isCrit));
// TODO: Glaive Rush (Gen IX effect)
if (randomFactor) if (randomFactor)
{ {
dmg *= 100 - RandomUniform(RNG_DAMAGE_MODIFIER, 0, 15); dmg *= 100 - RandomUniform(RNG_DAMAGE_MODIFIER, 0, 15);
dmg /= 100; dmg /= 100;
} }
DAMAGE_APPLY_MODIFIER(GetSameTypeAttackBonusModifier(battlerAtk, moveType, move, abilityAtk));
DAMAGE_APPLY_MODIFIER(typeEffectivenessModifier);
DAMAGE_APPLY_MODIFIER(GetBurnOrFrostBiteModifier(battlerAtk, move, abilityAtk));
DAMAGE_APPLY_MODIFIER(GetZMoveAgainstProtectionModifier(battlerDef));
DAMAGE_APPLY_MODIFIER(GetOtherModifiers(move, moveType, battlerAtk, battlerDef, isCrit, typeEffectivenessModifier, updateFlags));
if (dmg == 0) if (dmg == 0)
dmg = 1; dmg = 1;
return dmg; return dmg;
} }
#undef DAMAGE_APPLY_MODIFIER
s32 CalculateMoveDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, s32 fixedBasePower, bool32 isCrit, bool32 randomFactor, bool32 updateFlags) s32 CalculateMoveDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, s32 fixedBasePower, bool32 isCrit, bool32 randomFactor, bool32 updateFlags)
{ {
return DoMoveDamageCalc(move, battlerAtk, battlerDef, moveType, fixedBasePower, isCrit, randomFactor, return DoMoveDamageCalc(move, battlerAtk, battlerDef, moveType, fixedBasePower, isCrit, randomFactor,
@ -10775,34 +10896,6 @@ bool32 IsBattlerWeatherAffected(u8 battlerId, u32 weatherFlags)
return FALSE; return FALSE;
} }
// Utility Umbrella holders take normal damage from what would be rain- and sun-weakened attacks.
u32 ApplyWeatherDamageMultiplier(u8 battlerAtk, u16 move, u8 moveType, u32 dmg, u16 holdEffectAtk, u16 holdEffectDef)
{
if (WEATHER_HAS_EFFECT)
{
if (gBattleMoves[move].effect == EFFECT_HYDRO_STEAM && (gBattleWeather & B_WEATHER_SUN) && holdEffectAtk != HOLD_EFFECT_UTILITY_UMBRELLA)
dmg = ApplyModifier(UQ_4_12(1.5), dmg);
else if (holdEffectDef != HOLD_EFFECT_UTILITY_UMBRELLA)
{
if (gBattleWeather & B_WEATHER_RAIN)
{
if (moveType == TYPE_FIRE)
dmg = ApplyModifier(UQ_4_12(0.5), dmg);
else if (moveType == TYPE_WATER)
dmg = ApplyModifier(UQ_4_12(1.5), dmg);
}
else if (gBattleWeather & B_WEATHER_SUN)
{
if (moveType == TYPE_FIRE)
dmg = ApplyModifier(UQ_4_12(1.5), dmg);
else if (moveType == TYPE_WATER)
dmg = ApplyModifier(UQ_4_12(0.5), dmg);
}
}
}
return dmg;
}
// Gets move target before redirection effects etc. are applied // Gets move target before redirection effects etc. are applied
// Possible return values are defined in battle.h following MOVE_TARGET_SELECTED // Possible return values are defined in battle.h following MOVE_TARGET_SELECTED
u32 GetBattlerMoveTargetType(u8 battlerId, u16 move) u32 GetBattlerMoveTargetType(u8 battlerId, u16 move)

View File

@ -26,7 +26,7 @@ SINGLE_BATTLE_TEST("Contrary raises Attack when Intimidated", s16 damage)
HP_BAR(player, captureDamage: &results[i].damage); HP_BAR(player, captureDamage: &results[i].damage);
} }
FINALLY { FINALLY {
EXPECT_MUL_EQ(results[1].damage, Q_4_12(2.125), results[0].damage); EXPECT_MUL_EQ(results[1].damage, Q_4_12(2.25), results[0].damage);
} }
} }

View File

@ -36,15 +36,24 @@ SINGLE_BATTLE_TEST("Dry Skin increases damage taken from Fire-type moves by 25%"
PARAMETRIZE { ability = ABILITY_DRY_SKIN; } PARAMETRIZE { ability = ABILITY_DRY_SKIN; }
GIVEN { GIVEN {
ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE); ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE);
PLAYER(SPECIES_WOBBUFFET); ASSUME(gBattleMoves[MOVE_EMBER].power == 40);
OPPONENT(SPECIES_PARASECT) { Ability(ability); } ASSUME(gSpeciesInfo[SPECIES_PARASECT].types[0] == TYPE_BUG);
ASSUME(gSpeciesInfo[SPECIES_PARASECT].types[1] == TYPE_GRASS);
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] == TYPE_PSYCHIC);
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] == TYPE_PSYCHIC);
PLAYER(SPECIES_WOBBUFFET) { SpAttack(71); }
OPPONENT(SPECIES_PARASECT) { Ability(ability); SpDefense(165); }
} WHEN { } WHEN {
TURN { MOVE(player, MOVE_EMBER); } TURN { MOVE(player, MOVE_EMBER); }
} SCENE { } SCENE {
MESSAGE("Wobbuffet used Ember!"); MESSAGE("Wobbuffet used Ember!");
HP_BAR(opponent, captureDamage: &results[i].damage); HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY { } FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.25), results[1].damage); // Due to numerics related to rounding on each applied multiplier,
// the ability effect doesn't manifest as a 25% damage increase, but as a ~31% damage increase in this case.
// Values obtained from https://calc.pokemonshowdown.com (Neutral nature and 0 IVs on both sides)
EXPECT_EQ(results[0].damage, 52);
EXPECT_EQ(results[1].damage, 68);
} }
} }

66
test/ability_fluffy.c Normal file
View File

@ -0,0 +1,66 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_TACKLE].makesContact);
ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE);
ASSUME(gBattleMoves[MOVE_TACKLE].makesContact);
ASSUME(gBattleMoves[MOVE_FIRE_PUNCH].makesContact);
ASSUME(gBattleMoves[MOVE_FIRE_PUNCH].type == TYPE_FIRE);
ASSUME(P_GEN_7_POKEMON == TRUE);
}
SINGLE_BATTLE_TEST("Fluffy halves damage taken from moves that make direct contact", s16 damage)
{
u32 ability;
PARAMETRIZE { ability = ABILITY_KLUTZ; }
PARAMETRIZE { ability = ABILITY_FLUFFY; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_STUFFUL) { Ability(ability); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
} SCENE {
MESSAGE("Wobbuffet used Tackle!");
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Fluffy doubles damage taken from fire type moves", s16 damage)
{
u32 ability;
PARAMETRIZE { ability = ABILITY_KLUTZ; }
PARAMETRIZE { ability = ABILITY_FLUFFY; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_STUFFUL) { Ability(ability); }
} WHEN {
TURN { MOVE(player, MOVE_EMBER); }
} SCENE {
MESSAGE("Wobbuffet used Ember!");
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Fluffy does not alter damage of fire-type moves that make direct contact", s16 damage)
{
u32 ability;
PARAMETRIZE { ability = ABILITY_KLUTZ; }
PARAMETRIZE { ability = ABILITY_FLUFFY; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_STUFFUL) { Ability(ability); }
} WHEN {
TURN { MOVE(player, MOVE_FIRE_PUNCH); }
} SCENE {
MESSAGE("Wobbuffet used Fire Punch!");
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_EQ(results[0].damage, results[1].damage);
}
}

View File

@ -8,13 +8,21 @@ SINGLE_BATTLE_TEST("Swarm boosts Bug-type moves in a pinch", s16 damage)
PARAMETRIZE { hp = 33; } PARAMETRIZE { hp = 33; }
GIVEN { GIVEN {
ASSUME(gBattleMoves[MOVE_BUG_BITE].type == TYPE_BUG); ASSUME(gBattleMoves[MOVE_BUG_BITE].type == TYPE_BUG);
PLAYER(SPECIES_LEDYBA) { Ability(ABILITY_SWARM); MaxHP(99); HP(hp); } ASSUME(gBattleMoves[MOVE_BUG_BITE].power == 60);
OPPONENT(SPECIES_WOBBUFFET); ASSUME(gSpeciesInfo[SPECIES_LEDYBA].types[0] == TYPE_BUG);
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] == TYPE_PSYCHIC);
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] == TYPE_PSYCHIC);
PLAYER(SPECIES_LEDYBA) { Ability(ABILITY_SWARM); MaxHP(99); HP(hp); Attack(45); }
OPPONENT(SPECIES_WOBBUFFET) { Defense(121); }
} WHEN { } WHEN {
TURN { MOVE(player, MOVE_BUG_BITE); } TURN { MOVE(player, MOVE_BUG_BITE); }
} SCENE { } SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage); HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY { } FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); // Due to numerics related to rounding on each applied multiplier,
// the 50% move power increase doesn't manifest as a 50% damage increase, but as a 44% damage increase in this case.
// Values obtained from https://calc.pokemonshowdown.com (Neutral nature and 0 IVs on both sides)
EXPECT_EQ(results[0].damage, 50);
EXPECT_EQ(results[1].damage, 72);
} }
} }

78
test/damage_formula.c Normal file
View File

@ -0,0 +1,78 @@
#include "global.h"
#include "test_battle.h"
// From https://bulbapedia.bulbagarden.net/wiki/Damage#Example
SINGLE_BATTLE_TEST("Damage calculation matches Gen5+")
{
s16 dmg;
s16 expectedDamage;
PARAMETRIZE { expectedDamage = 196; }
PARAMETRIZE { expectedDamage = 192; }
PARAMETRIZE { expectedDamage = 192; }
PARAMETRIZE { expectedDamage = 192; }
PARAMETRIZE { expectedDamage = 184; }
PARAMETRIZE { expectedDamage = 184; }
PARAMETRIZE { expectedDamage = 184; }
PARAMETRIZE { expectedDamage = 180; }
PARAMETRIZE { expectedDamage = 180; }
PARAMETRIZE { expectedDamage = 180; }
PARAMETRIZE { expectedDamage = 172; }
PARAMETRIZE { expectedDamage = 172; }
PARAMETRIZE { expectedDamage = 172; }
PARAMETRIZE { expectedDamage = 168; }
PARAMETRIZE { expectedDamage = 168; }
PARAMETRIZE { expectedDamage = 168; }
GIVEN {
PLAYER(SPECIES_GLACEON) { Level(75); Attack(123); }
OPPONENT(SPECIES_GARCHOMP) { Defense(163); }
} WHEN {
TURN {
MOVE(player, MOVE_ICE_FANG, WITH_RNG(RNG_DAMAGE_MODIFIER, i));
}
}
SCENE{
MESSAGE("Glaceon used Ice Fang!");
HP_BAR(opponent, captureDamage: &dmg);
}
THEN{
EXPECT_EQ(expectedDamage, dmg);
}
}
SINGLE_BATTLE_TEST("Damage calculation matches Gen5+ (Muscle Band, crit)")
{
s16 dmg;
s16 expectedDamage;
PARAMETRIZE { expectedDamage = 324; }
PARAMETRIZE { expectedDamage = 316; }
PARAMETRIZE { expectedDamage = 312; }
PARAMETRIZE { expectedDamage = 312; }
PARAMETRIZE { expectedDamage = 304; }
PARAMETRIZE { expectedDamage = 304; }
PARAMETRIZE { expectedDamage = 300; }
PARAMETRIZE { expectedDamage = 300; }
PARAMETRIZE { expectedDamage = 292; }
PARAMETRIZE { expectedDamage = 292; }
PARAMETRIZE { expectedDamage = 288; }
PARAMETRIZE { expectedDamage = 288; }
PARAMETRIZE { expectedDamage = 280; }
PARAMETRIZE { expectedDamage = 276; }
PARAMETRIZE { expectedDamage = 276; }
PARAMETRIZE { expectedDamage = 268; }
GIVEN {
PLAYER(SPECIES_GLACEON) { Level(75); Attack(123); Item(ITEM_MUSCLE_BAND); }
OPPONENT(SPECIES_GARCHOMP) { Defense(163); }
} WHEN {
TURN {
MOVE(player, MOVE_ICE_FANG, WITH_RNG(RNG_DAMAGE_MODIFIER, i), criticalHit: TRUE);
}
}
SCENE{
MESSAGE("Glaceon used Ice Fang!");
HP_BAR(opponent, captureDamage: &dmg);
}
THEN{
EXPECT_EQ(expectedDamage, dmg);
}
}

90
test/status3.c Normal file
View File

@ -0,0 +1,90 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS {
ASSUME(gBattleMoves[MOVE_MINIMIZE].effect == EFFECT_MINIMIZE);
ASSUME(gBattleMoves[MOVE_STEAMROLLER].minimizeDoubleDamage);
ASSUME(gBattleMoves[MOVE_EARTHQUAKE].damagesUnderground);
ASSUME(gBattleMoves[MOVE_SURF].damagesUnderwater);
ASSUME(gBattleMoves[MOVE_TWISTER].damagesAirborneDoubleDamage);
}
SINGLE_BATTLE_TEST("Minimize causes the target to take double damage from certain moves", s16 damage)
{
bool32 useMinimize;
PARAMETRIZE { useMinimize = FALSE; }
PARAMETRIZE { useMinimize = TRUE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(2); }
} WHEN {
if (useMinimize)
TURN { MOVE(opponent, MOVE_MINIMIZE); MOVE(player, MOVE_STEAMROLLER); }
else
TURN { MOVE(player, MOVE_STEAMROLLER); }
} SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Being underground causes the target to take double damage from certain moves", s16 damage)
{
bool32 useDig;
PARAMETRIZE { useDig = FALSE; }
PARAMETRIZE { useDig = TRUE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(2); }
} WHEN {
if (useDig)
TURN { MOVE(opponent, MOVE_DIG); MOVE(player, MOVE_EARTHQUAKE); }
else
TURN { MOVE(player, MOVE_EARTHQUAKE); }
} SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Being underwater causes the target to take double damage from certain moves", s16 damage)
{
bool32 useDive;
PARAMETRIZE { useDive = FALSE; }
PARAMETRIZE { useDive = TRUE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(2); }
} WHEN {
if (useDive)
TURN { MOVE(opponent, MOVE_DIVE); MOVE(player, MOVE_SURF); }
else
TURN { MOVE(player, MOVE_SURF); }
} SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Being airborne causes the target to take double damage from certain moves", s16 damage)
{
bool32 useDive;
PARAMETRIZE { useDive = FALSE; }
PARAMETRIZE { useDive = TRUE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(2); }
} WHEN {
if (useDive)
TURN { MOVE(opponent, MOVE_FLY); MOVE(player, MOVE_TWISTER); }
else
TURN { MOVE(player, MOVE_TWISTER); }
} SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage);
}
}

View File

@ -81,7 +81,6 @@ SINGLE_BATTLE_TEST("Snow halves the power of Solar Beam", s16 damage)
SINGLE_BATTLE_TEST("Snow halves the power of Solar Blade", s16 damage) SINGLE_BATTLE_TEST("Snow halves the power of Solar Blade", s16 damage)
{ {
u16 move; u16 move;
KNOWN_FAILING; // fails bc the bp of solar blade gets rounded up which leads to slightly incorrect calcs down the line
PARAMETRIZE{ move = MOVE_CELEBRATE; } PARAMETRIZE{ move = MOVE_CELEBRATE; }
PARAMETRIZE{ move = MOVE_SNOWSCAPE; } PARAMETRIZE{ move = MOVE_SNOWSCAPE; }
GIVEN { GIVEN {