Fix for Levitate and Mold Breaker being seen correctly by switch AI, with Levitate tests (#7748)

This commit is contained in:
grintoul 2025-09-17 17:32:50 +01:00 committed by GitHub
parent f671b130a6
commit b365221a0c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 130 additions and 8 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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); }
}
}