diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 415c4fb938..ea02bc5917 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -132,6 +132,8 @@ bool32 ShouldUseRecoilMove(u32 battlerAtk, u32 battlerDef, u32 recoilDmg, u32 mo bool32 ShouldAbsorb(u32 battlerAtk, u32 battlerDef, u32 move, s32 damage); bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent); bool32 ShouldSetScreen(u32 battlerAtk, u32 battlerDef, enum BattleMoveEffects moveEffect); +bool32 ShouldCureStatus(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData); +bool32 ShouldCureStatusWithItem(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData); enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 move, u32 moveIndex); bool32 IsRecycleEncouragedItem(u32 item); bool32 ShouldRestoreHpBerry(u32 battlerAtk, u32 item); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index ec13d78a94..f48956cd5b 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -2194,10 +2194,13 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); else if (battlerDef == BATTLE_PARTNER(battlerAtk)) break; //Always heal your ally - else if (AI_BattlerAtMaxHp(battlerAtk)) - ADJUST_SCORE(-10); - else if (aiData->hpPercents[battlerAtk] >= 90) - ADJUST_SCORE(-8); //No point in healing, but should at least do it if nothing better + else if (!ShouldCureStatus(battlerAtk, battlerDef, aiData)) + { + if (AI_BattlerAtMaxHp(battlerAtk)) + ADJUST_SCORE(-10); + else if (aiData->hpPercents[battlerAtk] >= 90) + ADJUST_SCORE(-8); //No point in healing, but should at least do it if nothing better + } break; case EFFECT_RECOIL_IF_MISS: if (aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD && gAiLogicData->moveAccuracy[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex] < 75 @@ -3602,8 +3605,18 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (gBattleMons[battlerAtkPartner].status1 & STATUS1_ANY) { if (gBattleMons[battlerAtkPartner].status1 & STATUS1_CAN_MOVE) + { + if (ShouldCureStatus(battlerAtk, battlerAtkPartner, aiData)) + ADJUST_SCORE(DECENT_EFFECT); + } + else + { + ADJUST_SCORE(DECENT_EFFECT); + } + + if ((!IsBattlerAlive(LEFT_FOE(battlerAtk)) || ShouldRecover(battlerAtk, LEFT_FOE(battlerAtk), move, 50)) + && (!IsBattlerAlive(RIGHT_FOE(battlerAtk)) || ShouldRecover(battlerAtk, RIGHT_FOE(battlerAtk), move, 50))) RETURN_SCORE_PLUS(WEAK_EFFECT); - RETURN_SCORE_PLUS(GOOD_EFFECT); } break; case EFFECT_SWAGGER: @@ -4557,6 +4570,15 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru if (ShouldUseWishAromatherapy(battlerAtk, battlerDef, move)) ADJUST_SCORE(DECENT_EFFECT); break; + case EFFECT_PURIFY: + if (gBattleMons[battlerDef].status1 & STATUS1_ANY) + { + if (ShouldCureStatus(battlerAtk, battlerDef, aiData)) + ADJUST_SCORE(GOOD_EFFECT); + if (ShouldRecover(battlerAtk, battlerDef, move, 50)) + RETURN_SCORE_PLUS(WEAK_EFFECT); + } + break; case EFFECT_CURSE: if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GHOST)) { @@ -5909,6 +5931,17 @@ static s32 AI_CalcAdditionalEffectScore(u32 battlerAtk, u32 battlerDef, u32 move if (ShouldSetScreen(battlerAtk, battlerDef, EFFECT_AURORA_VEIL)) ADJUST_SCORE(DECENT_EFFECT); break; + case MOVE_EFFECT_REMOVE_STATUS: + if (gBattleMons[battlerDef].status1 & GetMoveEffectArg_Status(move)) + { + if (ShouldCureStatus(battlerAtk, battlerDef, aiData)) + ADJUST_SCORE(DECENT_EFFECT); + else if (aiData->holdEffects[battlerDef] == HOLD_EFFECT_FLAME_ORB || aiData->holdEffects[battlerDef] == HOLD_EFFECT_TOXIC_ORB) + ADJUST_SCORE(WEAK_EFFECT); + else + ADJUST_SCORE(BAD_EFFECT); + } + break; default: break; } diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 11a54290cf..bc2dedbcab 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -2505,18 +2505,13 @@ static bool32 ShouldUseItem(u32 battler) shouldUse = AI_ShouldHeal(battler, healAmount); break; case EFFECT_ITEM_CURE_STATUS: - if (itemEffects[3] & ITEM3_SLEEP && gBattleMons[battler].status1 & STATUS1_SLEEP) - shouldUse = TRUE; - if (itemEffects[3] & ITEM3_POISON && gBattleMons[battler].status1 & STATUS1_PSN_ANY) - shouldUse = TRUE; - if (itemEffects[3] & ITEM3_BURN && gBattleMons[battler].status1 & STATUS1_BURN) - shouldUse = TRUE; - if (itemEffects[3] & ITEM3_FREEZE && gBattleMons[battler].status1 & STATUS1_ICY_ANY) - shouldUse = TRUE; - if (itemEffects[3] & ITEM3_PARALYSIS && gBattleMons[battler].status1 & STATUS1_PARALYSIS) - shouldUse = TRUE; - if (itemEffects[3] & ITEM3_CONFUSION && gBattleMons[battler].volatiles.confusionTurns > 0) - shouldUse = TRUE; + if ((itemEffects[3] & ITEM3_SLEEP && gBattleMons[battler].status1 & STATUS1_SLEEP) + || (itemEffects[3] & ITEM3_POISON && gBattleMons[battler].status1 & STATUS1_PSN_ANY) + || (itemEffects[3] & ITEM3_BURN && gBattleMons[battler].status1 & STATUS1_BURN) + || (itemEffects[3] & ITEM3_FREEZE && gBattleMons[battler].status1 & STATUS1_ICY_ANY) + || (itemEffects[3] & ITEM3_PARALYSIS && gBattleMons[battler].status1 & STATUS1_PARALYSIS) + || (itemEffects[3] & ITEM3_CONFUSION && gBattleMons[battler].volatiles.confusionTurns > 0)) + shouldUse = ShouldCureStatusWithItem(battler, battler, gAiLogicData); break; case EFFECT_ITEM_INCREASE_STAT: case EFFECT_ITEM_INCREASE_ALL_STATS: diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 3d9457cbde..3b2c12e63f 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -3931,6 +3931,102 @@ bool32 ShouldSetScreen(u32 battlerAtk, u32 battlerDef, enum BattleMoveEffects mo return FALSE; } +static bool32 ShouldCureStatusInternal(u32 battlerAtk, u32 battlerDef, bool32 usingItem, struct AiLogicData *aiData) +{ + bool32 targetingSelf = (battlerAtk == battlerDef); + bool32 targetingAlly = IsTargetingPartner(battlerAtk, battlerDef); + u32 status = gBattleMons[battlerDef].status1; + + if (status & STATUS1_SLEEP) + { + if (targetingAlly || targetingSelf) + { + if (HasMoveWithEffect(battlerDef, EFFECT_SLEEP_TALK) || HasMoveWithEffect(battlerDef, EFFECT_SNORE)) + return FALSE; + else + return usingItem || targetingAlly; + } + return FALSE; + } + + if (status & STATUS1_FREEZE) + { + if (targetingAlly || targetingSelf) + { + if (HasThawingMove(battlerDef)) + return FALSE; + return usingItem || targetingAlly; + } + return FALSE; + } + + bool32 isHarmless = FALSE; + + if (DoesBattlerBenefitFromAllVolatileStatus(battlerDef, aiData->abilities[battlerDef])) + isHarmless = TRUE; + + if (status & STATUS1_PSN_ANY) + { + if (aiData->holdEffects[battlerDef] == HOLD_EFFECT_TOXIC_ORB) + return FALSE; + + if (aiData->abilities[battlerDef] == ABILITY_POISON_HEAL) + isHarmless = TRUE; + + if (aiData->abilities[battlerDef] == ABILITY_TOXIC_BOOST && !isHarmless) + { + if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)) + isHarmless = TRUE; + else if (!(targetingSelf || targetingAlly) && !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)) + isHarmless = TRUE; + } + } + + if (status & STATUS1_BURN) + { + if (aiData->holdEffects[battlerDef] == HOLD_EFFECT_FLAME_ORB) + return FALSE; + + if (aiData->abilities[battlerDef] == ABILITY_FLARE_BOOST && !isHarmless) + { + if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)) + isHarmless = TRUE; + else if (!(targetingSelf || targetingAlly) && !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)) + isHarmless = TRUE; + } + } + +/* + if (status & STATUS1_PARALYSIS) + if (status & STATUS1_FROSTBITE) +*/ + + if (isHarmless) + { + if (targetingSelf || targetingAlly) + return FALSE; + else + return TRUE; + } + else + { + if (targetingSelf || targetingAlly) + return TRUE; + else + return FALSE; + } +} + +bool32 ShouldCureStatus(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData) +{ + return ShouldCureStatusInternal(battlerAtk, battlerDef, FALSE, aiData); +} + +bool32 ShouldCureStatusWithItem(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData) +{ + return ShouldCureStatusInternal(battlerAtk, battlerDef, TRUE, aiData); +} + // Partner Logic bool32 IsBattle1v1() { diff --git a/test/battle/ai/ai_check_viability.c b/test/battle/ai/ai_check_viability.c index 8b6fd9797c..77de2ba1cb 100644 --- a/test/battle/ai/ai_check_viability.c +++ b/test/battle/ai/ai_check_viability.c @@ -439,3 +439,22 @@ AI_DOUBLE_BATTLE_TEST("AI sees type-changing moves as the correct type") TURN { NOT_EXPECT_MOVE(opponentLeft, fieldStatus); } } } + +AI_SINGLE_BATTLE_TEST("AI uses Sparkling Aria to cure an enemy with Guts") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_GUTS; } + PARAMETRIZE { ability = ABILITY_BULLETPROOF; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_URSALUNA) { Ability(ability); Moves(MOVE_HEADLONG_RUSH, MOVE_CELEBRATE); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_PRIMARINA) { Moves(MOVE_SPARKLING_ARIA, MOVE_SCALD); } + } WHEN { + if (ability == ABILITY_GUTS) + TURN { EXPECT_MOVE(opponent, MOVE_SPARKLING_ARIA); } + else + TURN { EXPECT_MOVE(opponent, MOVE_SCALD); } + } +} diff --git a/test/battle/move_effect/purify.c b/test/battle/move_effect/purify.c index 5f1651e96f..425d0a99a9 100644 --- a/test/battle/move_effect/purify.c +++ b/test/battle/move_effect/purify.c @@ -22,5 +22,45 @@ AI_DOUBLE_BATTLE_TEST("AI uses Purify") } } +AI_SINGLE_BATTLE_TEST("AI uses Purify to heal an enemy with Guts") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_GUTS; } + PARAMETRIZE { ability = ABILITY_BULLETPROOF; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_URSALUNA) { Ability(ability); Moves(MOVE_HEADLONG_RUSH, MOVE_CELEBRATE); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HEADBUTT, MOVE_PURIFY); } + } WHEN { + if (ability == ABILITY_GUTS) + TURN { EXPECT_MOVE(opponent, MOVE_PURIFY); } + else + TURN { NOT_EXPECT_MOVE(opponent, MOVE_PURIFY); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI does not use Purify to heal an ally with Guts") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_GUTS; } + PARAMETRIZE { ability = ABILITY_BULLETPROOF; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HEADBUTT, MOVE_PURIFY); } + OPPONENT(SPECIES_URSALUNA) { Ability(ability); Moves(MOVE_HEADLONG_RUSH); Status1(STATUS1_BURN); } + } WHEN { + if (ability == ABILITY_GUTS) + TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_PURIFY); } + else + TURN { EXPECT_MOVE(opponentLeft, MOVE_PURIFY, target: opponentRight); } + } +} + TO_DO_BATTLE_TEST("TODO: Write Purify (Move Effect) test titles") TO_DO_BATTLE_TEST("Purify doesn't heal HP if the target has Comatose")