Fix AI semi-invulnerable move handling and simplify switching logic (#9180)

This commit is contained in:
GGbond 2026-02-17 00:16:28 +08:00 committed by GitHub
parent a3d9aa7ae2
commit 9119a6cc53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 37 additions and 14 deletions

View File

@ -480,7 +480,6 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler)
u32 opposingBattler = GetOppositeBattler(battler);
u32 incomingMove = GetIncomingMove(battler, opposingBattler, gAiLogicData);
enum Type incomingType = CheckDynamicMoveType(GetBattlerMon(opposingBattler), incomingMove, opposingBattler, MON_IN_BATTLE);
bool32 isOpposingBattlerChargingOrInvulnerable = !BreaksThroughSemiInvulnerablity(opposingBattler, incomingMove) || IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove);
s32 i, j;
if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING))
@ -530,42 +529,42 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler)
{
absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_FLASH_FIRE;
}
if (incomingType == TYPE_WATER || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_WATER))
if (incomingType == TYPE_WATER)
{
absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_WATER_ABSORB;
absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_DRY_SKIN;
if (GetConfig(B_REDIRECT_ABILITY_IMMUNITY) >= GEN_5)
absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_STORM_DRAIN;
}
if (incomingType == TYPE_ELECTRIC || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_ELECTRIC))
if (incomingType == TYPE_ELECTRIC)
{
absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_VOLT_ABSORB;
absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_MOTOR_DRIVE;
if (GetConfig(B_REDIRECT_ABILITY_IMMUNITY) >= GEN_5)
absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_LIGHTNING_ROD;
}
if (incomingType == TYPE_GRASS || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GRASS))
if (incomingType == TYPE_GRASS)
{
absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_SAP_SIPPER;
}
if (incomingType == TYPE_GROUND || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GROUND))
if (incomingType == TYPE_GROUND)
{
absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_EARTH_EATER;
absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_LEVITATE;
}
if (IsSoundMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsSoundMove(incomingMove)))
if (IsSoundMove(incomingMove))
{
absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_SOUNDPROOF;
}
if (IsBallisticMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsBallisticMove(incomingMove)))
if (IsBallisticMove(incomingMove))
{
absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_BULLETPROOF;
}
if (IsWindMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsWindMove(incomingMove)))
if (IsWindMove(incomingMove))
{
absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_WIND_RIDER;
}
if (IsPowderMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsPowderMove(incomingMove)))
if (IsPowderMove(incomingMove))
{
if (GetConfig(B_POWDER_OVERCOAT) >= GEN_6)
absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_OVERCOAT;
@ -617,14 +616,23 @@ static bool32 ShouldSwitchIfOpponentChargingOrInvulnerable(u32 battler)
{
u32 opposingBattler = GetOppositeBattler(battler);
u32 incomingMove = GetIncomingMove(battler, opposingBattler, gAiLogicData);
bool32 isOpposingBattlerChargingOrInvulnerable = !BreaksThroughSemiInvulnerablity(opposingBattler, incomingMove) || IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove);
enum BattleMoveEffects effect = GetMoveEffect(incomingMove);
if (IsDoubleBattle() || !(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING))
return FALSE;
// Two-turn attacks that charge without entering semi-invulnerable state (e.g. Solar Beam).
// First turn of Fly/Dive/Bounce/Sky Drop: move is selected this turn but user is not yet semi-invulnerable.
// Opponent is already semi-invulnerable.
if (!(IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove)
|| ((effect == EFFECT_SEMI_INVULNERABLE || effect == EFFECT_SKY_DROP) && !IsSemiInvulnerable(opposingBattler, CHECK_ALL))
|| IsSemiInvulnerable(opposingBattler, CHECK_ALL)))
{
return FALSE;
}
// In a world with a unified ShouldSwitch function, also want to check whether we already win 1v1 and if we do don't switch; not worth doubling the HasBadOdds computation for now
if (isOpposingBattlerChargingOrInvulnerable && gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE && RandomPercentage(RNG_AI_SWITCH_FREE_TURN, GetSwitchChance(SHOULD_SWITCH_FREE_TURN)))
if (gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE && RandomPercentage(RNG_AI_SWITCH_FREE_TURN, GetSwitchChance(SHOULD_SWITCH_FREE_TURN)))
return SetSwitchinAndSwitch(battler, PARTY_SIZE);
return FALSE;
@ -654,7 +662,7 @@ static bool32 ShouldSwitchIfTrapperInParty(u32 battler)
for (i = firstId; i < lastId; i++)
{
if (IsAceMon(battler, i))
return FALSE;
continue;
monAbility = GetMonAbility(&party[i]);

View File

@ -54,7 +54,7 @@ static bool32 AI_IsDoubleSpreadMove(u32 battlerAtk, u32 move)
if (moveTargetType == MOVE_TARGET_BOTH && battlerAtk == BATTLE_PARTNER(battlerDef))
continue;
if (IsBattlerAlive(battlerDef) && !IsSemiInvulnerable(battlerDef, move))
if (IsBattlerAlive(battlerDef) && (!IsSemiInvulnerable(battlerDef, CHECK_ALL) || BreaksThroughSemiInvulnerablity(battlerDef, move)))
numOfTargets++;
}

View File

@ -1030,6 +1030,21 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if player's m
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out on turn 1 if it predicts a semi-invulnerable move and it has a good switchin")
{
PASSES_RANDOMLY(PREDICT_MOVE_CHANCE, 100, RNG_AI_PREDICT_MOVE);
PASSES_RANDOMLY(SHOULD_SWITCH_FREE_TURN_PERCENTAGE, 100, RNG_AI_SWITCH_FREE_TURN);
GIVEN {
ASSUME(GetMoveType(MOVE_DIVE) == TYPE_WATER);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE);
PLAYER(SPECIES_LUVDISC) { Level(1); Moves(MOVE_DIVE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_PIKACHU) { Moves(MOVE_THUNDERBOLT); }
} WHEN {
TURN { MOVE(player, MOVE_DIVE); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has an absorber but current mon has SE move 33% of the time")
{
PASSES_RANDOMLY(33, 100, RNG_AI_SWITCH_ABSORBING_STAY_IN);