diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 625ea3a9e1..b9e90ee4fd 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -1443,6 +1443,26 @@ bool32 CanAIFaintTarget(u32 battlerAtk, u32 battlerDef, u32 numHits) return FALSE; } +// Can battler KO the target ignoring any Endure effects (Sturdy, Focus Sash, etc.) +bool32 CanBattlerKOTargetIgnoringSturdy(u32 battlerAtk, u32 battlerDef) +{ + struct AiLogicData *aiData = gAiLogicData; + s32 moveIndex, dmg; + u16 *moves = GetMovesArray(battlerAtk); + u32 moveLimitations = aiData->moveLimitations[battlerAtk]; + + for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) + { + if (IsMoveUnusable(moveIndex, moves[moveIndex], moveLimitations)) + continue; + dmg = AI_GetDamage(battlerAtk, battlerDef, moveIndex, AI_ATTACKING, aiData); + + if (gBattleMons[battlerDef].hp <= dmg && CanEndureHit(battlerAtk, battlerDef, moves[moveIndex])) + return TRUE; + } + return FALSE; +} + bool32 CanTargetMoveFaintAi(u32 move, u32 battlerDef, u32 battlerAtk, u32 nHits) { u32 indexSlot = GetMoveSlot(GetMovesArray(battlerDef), move); @@ -4220,6 +4240,35 @@ bool32 IsRecycleEncouragedItem(u32 item) return FALSE; } +bool32 HasMoveThatChangesKOThreshold(u32 battlerId, u32 noOfHitsToFaint, u32 aiIsFaster) +{ + s32 i; + u16 *moves = GetMovesArray(battlerId); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] == MOVE_NONE || moves[i] == MOVE_UNAVAILABLE) + continue; + if (noOfHitsToFaint <= 2) + { + if (GetMovePriority(moves[i]) > 0) + return TRUE; + + switch (gMovesInfo[moves[i]].additionalEffects[i].moveEffect) + { + case MOVE_EFFECT_SPD_MINUS_1: + case MOVE_EFFECT_SPD_MINUS_2: + { + if(aiIsFaster) + return TRUE; + } + } + } + } + + return FALSE; +} + static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef, enum StatChange statId, bool32 considerContrary) { enum AIScore tempScore = NO_INCREASE; @@ -4268,6 +4317,14 @@ static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef, || HasBattlerSideMoveWithAdditionalEffect(battlerDef, MOVE_EFFECT_HAZE))) return NO_INCREASE; + // Don't increase stats if AI could KO target through Sturdy effect, as otherwise it always 2HKOs + if (CanBattlerKOTargetIgnoringSturdy(battlerAtk, battlerDef)) + return NO_INCREASE; + + // Don't increase stats if player has a move that can change the KO threshold + if (HasMoveThatChangesKOThreshold(battlerDef, noOfHitsToFaint, aiIsFaster)) + return NO_INCREASE; + // Predicting switch if (IsBattlerPredictedToSwitch(battlerDef)) { diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index 7eb06dd5d4..5202afe606 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -923,3 +923,25 @@ AI_SINGLE_BATTLE_TEST("AI will prefer resisted move over failing move") TURN { MOVE(player, MOVE_ABSORB); EXPECT_MOVE(opponent, MOVE_MEGA_DRAIN);} } } + +AI_SINGLE_BATTLE_TEST("AI won't setup if it can KO through Sturdy effect") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_SKARMORY) { Ability(ABILITY_STURDY); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_MOLTRES) { Moves(MOVE_FIRE_BLAST, MOVE_AGILITY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_FIRE_BLAST); } + } +} + +AI_SINGLE_BATTLE_TEST("AI won't setup if otherwise good scenario is changed by the presence of priority") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_FLOATZEL) { Speed(2); Moves(MOVE_AQUA_JET, MOVE_SURF); } + OPPONENT(SPECIES_DONPHAN) { Speed(5); Moves(MOVE_BULK_UP, MOVE_EARTHQUAKE); } + } WHEN { + TURN { MOVE(player, MOVE_SURF); EXPECT_MOVE(opponent, MOVE_EARTHQUAKE); } + } +}