diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index f92112063d..bf06a8e6a1 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -28,6 +28,17 @@ static u32 GetAIEffectGroup(enum BattleMoveEffects effect); static u32 GetAIEffectGroupFromMove(u32 battler, u32 move); // Functions +static u32 AI_GetMoldBreakerSanitizedAbility(u32 battlerAtk, u32 abilityAtk, u32 abilityDef, u32 holdEffectDef, u32 move) +{ + if (MoveIgnoresTargetAbility(move)) + return ABILITY_NONE; + + if (holdEffectDef != HOLD_EFFECT_ABILITY_SHIELD && IsMoldBreakerTypeAbility(battlerAtk, abilityAtk)) + return ABILITY_NONE; + + return abilityDef; +} + static bool32 AI_IsDoubleSpreadMove(u32 battlerAtk, u32 move) { u32 numOfTargets = 0; @@ -896,10 +907,10 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u ctx.updateFlags = FALSE; ctx.weather = weather; ctx.fixedBasePower = SetFixedMoveBasePower(battlerAtk, move); - ctx.abilityAtk = aiData->abilities[battlerAtk]; - ctx.abilityDef = aiData->abilities[battlerDef]; ctx.holdEffectAtk = aiData->holdEffects[battlerAtk]; ctx.holdEffectDef = aiData->holdEffects[battlerDef]; + ctx.abilityAtk = aiData->abilities[battlerAtk]; + ctx.abilityDef = AI_GetMoldBreakerSanitizedAbility(battlerAtk, ctx.abilityAtk, aiData->abilities[battlerDef], ctx.holdEffectDef, move); ctx.typeEffectivenessModifier = CalcTypeEffectivenessMultiplier(&ctx); u32 movePower = GetMovePower(move); diff --git a/src/battle_util.c b/src/battle_util.c index 76ed12a9be..b348b1beb4 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -7732,7 +7732,7 @@ enum IronBallCheck }; // Only called directly when calculating damage type effectiveness, and Iron Ball's type effectiveness mechanics -static bool32 IsBattlerGroundedInverseCheck(u32 battler, enum InverseBattleCheck checkInverse, enum IronBallCheck checkIronBall) +static bool32 IsBattlerGroundedInverseCheck(u32 battler, u32 ability, enum InverseBattleCheck checkInverse, enum IronBallCheck checkIronBall) { enum ItemHoldEffect holdEffect = GetBattlerHoldEffect(battler, TRUE); @@ -7750,7 +7750,7 @@ static bool32 IsBattlerGroundedInverseCheck(u32 battler, enum InverseBattleCheck return FALSE; if (holdEffect == HOLD_EFFECT_AIR_BALLOON) return FALSE; - if (gAiLogicData->aiCalcInProgress ? gAiLogicData->abilities[battler] == ABILITY_LEVITATE : IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_LEVITATE)) + if (ability == ABILITY_LEVITATE) return FALSE; if (IS_BATTLER_OF_TYPE(battler, TYPE_FLYING) && (!(checkInverse == INVERSE_BATTLE) || !FlagGet(B_FLAG_INVERSE_BATTLE))) return FALSE; @@ -7759,7 +7759,7 @@ static bool32 IsBattlerGroundedInverseCheck(u32 battler, enum InverseBattleCheck bool32 IsBattlerGrounded(u32 battler) { - return IsBattlerGroundedInverseCheck(battler, NOT_INVERSE_BATTLE, CHECK_IRON_BALL); + return IsBattlerGroundedInverseCheck(battler, GetBattlerAbility(battler), NOT_INVERSE_BATTLE, CHECK_IRON_BALL); } u32 GetMoveSlot(u16 *moves, u32 move) @@ -9713,7 +9713,7 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(struct DamageCont if (B_GLARE_GHOST < GEN_4 && ctx->move == MOVE_GLARE && IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_GHOST)) modifier = UQ_4_12(0.0); } - else if (ctx->moveType == TYPE_GROUND && !IsBattlerGroundedInverseCheck(ctx->battlerDef, INVERSE_BATTLE, CHECK_IRON_BALL) && !(MoveIgnoresTypeIfFlyingAndUngrounded(ctx->move))) + else if (ctx->moveType == TYPE_GROUND && !IsBattlerGroundedInverseCheck(ctx->battlerDef, ctx->abilityDef, INVERSE_BATTLE, CHECK_IRON_BALL) && !(MoveIgnoresTypeIfFlyingAndUngrounded(ctx->move))) { modifier = UQ_4_12(0.0); if (ctx->updateFlags && ctx->abilityDef == ABILITY_LEVITATE) @@ -9743,7 +9743,7 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(struct DamageCont && ctx->moveType == TYPE_GROUND && IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_FLYING) && GetBattlerHoldEffect(ctx->battlerDef, TRUE) == HOLD_EFFECT_IRON_BALL - && !IsBattlerGroundedInverseCheck(ctx->battlerDef, NOT_INVERSE_BATTLE, IGNORE_IRON_BALL) + && !IsBattlerGroundedInverseCheck(ctx->battlerDef, ctx->abilityAtk, NOT_INVERSE_BATTLE, IGNORE_IRON_BALL) && !FlagGet(B_FLAG_INVERSE_BATTLE)) { modifier = UQ_4_12(1.0); diff --git a/test/battle/ability/levitate.c b/test/battle/ability/levitate.c index a1c1dc927d..348d347f3b 100644 --- a/test/battle/ability/levitate.c +++ b/test/battle/ability/levitate.c @@ -1,4 +1,115 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Levitate (Ability) test titles") +SINGLE_BATTLE_TEST("Levitate activates when targeted by ground type moves") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_MUD_SLAP) == TYPE_GROUND); + PLAYER(SPECIES_LUNATONE) { Ability(ABILITY_LEVITATE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_MUD_SLAP); } + } SCENE { + ABILITY_POPUP(player, ABILITY_LEVITATE); + MESSAGE("Lunatone makes Ground-type moves miss with Levitate!"); + } +} + +SINGLE_BATTLE_TEST("Levitate does not activate if protected") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_MUD_SLAP) == TYPE_GROUND); + PLAYER(SPECIES_LUNATONE) { Ability(ABILITY_LEVITATE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PROTECT); MOVE(opponent, MOVE_MUD_SLAP); } + } SCENE { + NONE_OF { + ABILITY_POPUP(player, ABILITY_LEVITATE); + MESSAGE("Lunatone makes Ground-type moves miss with Levitate!"); + } + } +} + +SINGLE_BATTLE_TEST("Levitate does not activate on status moves") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SAND_ATTACK) == TYPE_GROUND); + ASSUME(GetMoveCategory(MOVE_SAND_ATTACK) == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_LUNATONE) { Ability(ABILITY_LEVITATE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SAND_ATTACK); } + } SCENE { + NONE_OF { + ABILITY_POPUP(player, ABILITY_LEVITATE); + MESSAGE("Lunatone makes Ground-type moves miss with Levitate!"); + } + } +} + +SINGLE_BATTLE_TEST("Levitate does not activate if attacked by an opponent with Mold Breaker") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_MUD_SLAP) == TYPE_GROUND); + PLAYER(SPECIES_LUNATONE) { Ability(ABILITY_LEVITATE); } + OPPONENT(SPECIES_TINKATON) { Ability(ABILITY_MOLD_BREAKER); } + } WHEN { + TURN { MOVE(player, MOVE_PROTECT); MOVE(opponent, MOVE_MUD_SLAP); } + } SCENE { + NONE_OF { + ABILITY_POPUP(player, ABILITY_LEVITATE); + MESSAGE("Lunatone makes Ground-type moves miss with Levitate!"); + } + } +} + +DOUBLE_BATTLE_TEST("Levitate does not cause single remaining target to take higher damage") +{ + s16 damage[3]; + GIVEN { + PLAYER(SPECIES_REGIROCK) { Speed(1); } + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + OPPONENT(SPECIES_LUNATONE) { Speed(3); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + } WHEN { + TURN { MOVE(playerRight, MOVE_CELEBRATE); MOVE(playerLeft, MOVE_EARTHQUAKE); } + TURN { MOVE(playerRight, MOVE_MEMENTO, target:opponentLeft); MOVE(playerLeft, MOVE_EARTHQUAKE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft); + HP_BAR(playerRight, captureDamage: &damage[0]); + HP_BAR(opponentRight, captureDamage: &damage[1]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEMENTO, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft); + HP_BAR(opponentRight, captureDamage: &damage[2]); + + } THEN { + EXPECT_EQ(damage[0], damage[1]); + EXPECT_EQ(damage[1], damage[2]); + } +} + +AI_SINGLE_BATTLE_TEST("Levitate is seen correctly by switch AI") +{ + u32 ability = ABILITY_NONE, item = ITEM_NONE; + + PARAMETRIZE { ability = ABILITY_OWN_TEMPO, item = ITEM_NONE ; } + PARAMETRIZE { ability = ABILITY_MOLD_BREAKER, item = ITEM_NONE ; } + PARAMETRIZE { ability = ABILITY_MOLD_BREAKER, item = ITEM_ABILITY_SHIELD ; } + + GIVEN { + ASSUME(GetMoveType(MOVE_MUD_SLAP) == TYPE_GROUND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_TINKATON) { Ability(ability); Speed(3); } + OPPONENT(SPECIES_PONYTA) { Level(1); Item(ITEM_EJECT_PACK); Moves(MOVE_OVERHEAT); Speed(4); } // Forces switchout + OPPONENT(SPECIES_VIKAVOLT) { HP(1); Speed(1); Ability(ABILITY_LEVITATE); Moves(MOVE_FLAMETHROWER); Item(item); } + OPPONENT(SPECIES_HYPNO) { Speed(1); Moves(MOVE_IRON_HEAD); } + } WHEN { + if ((ability == ABILITY_MOLD_BREAKER) && (item != ITEM_ABILITY_SHIELD)) + TURN { MOVE(player, MOVE_MUD_SLAP); EXPECT_SEND_OUT(opponent, 2); } + else + TURN { MOVE(player, MOVE_MUD_SLAP); EXPECT_SEND_OUT(opponent, 1); } + } +} +