From 54928726cb3a046f7eae7cfd2da086c85a4f45df Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Tue, 8 Apr 2025 11:47:45 -0400 Subject: [PATCH] AI smarter status handling (#6550) --- include/battle_ai_util.h | 6 +- src/battle_ai_main.c | 18 ++++-- src/battle_ai_util.c | 125 +++++++++++++++++++++++++++++++-------- test/battle/ai/ai.c | 18 ++++++ 4 files changed, 134 insertions(+), 33 deletions(-) diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index d9e8edd018..2b97d86dd5 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -177,11 +177,13 @@ bool32 AI_CanGetFrostbite(u32 battler, u32 ability); bool32 AI_CanBeConfused(u32 battlerAtk, u32 battlerDef, u32 move, u32 ability); bool32 IsBattlerIncapacitated(u32 battler, u32 ability); bool32 AI_CanPutToSleep(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 partnerMove); -bool32 ShouldPoisonSelf(u32 battler, u32 ability); +bool32 ShouldPoison(u32 battlerAtk, u32 battlerDef); bool32 AI_CanPoison(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 partnerMove); bool32 AI_CanParalyze(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 partnerMove); bool32 AI_CanConfuse(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove); -bool32 ShouldBurnSelf(u32 battler, u32 ability); +bool32 ShouldBurn(u32 battlerAtk, u32 battlerDef); +bool32 ShouldFreezeOrFrostbite(u32 battlerAtk, u32 battlerDef); +bool32 ShouldParalyze(u32 battlerAtk, u32 battlerDef); bool32 AI_CanBurn(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove); bool32 AI_CanGiveFrostbite(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove); bool32 AI_CanBeInfatuated(u32 battlerAtk, u32 battlerDef, u32 defAbility); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index a9f9a8e5e2..d6f0a3a671 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -1456,6 +1456,8 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_TOXIC: if (!AI_CanPoison(battlerAtk, battlerDef, abilityDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); + if (!ShouldPoison(battlerAtk, battlerDef)) + ADJUST_SCORE(-5); break; case EFFECT_LIGHT_SCREEN: if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_LIGHTSCREEN @@ -1499,6 +1501,8 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_PARALYZE: if (!AI_CanParalyze(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) ADJUST_SCORE(-10); + if (!ShouldParalyze(battlerAtk, battlerDef)) + ADJUST_SCORE(-5); break; case EFFECT_SUBSTITUTE: if (gBattleMons[battlerAtk].status2 & STATUS2_SUBSTITUTE || aiData->abilities[battlerDef] == ABILITY_INFILTRATOR) @@ -1791,6 +1795,8 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_WILL_O_WISP: if (!AI_CanBurn(battlerAtk, battlerDef, aiData->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) ADJUST_SCORE(-10); + if (!ShouldBurn(battlerAtk, battlerDef)) + ADJUST_SCORE(-5); break; case EFFECT_MEMENTO: if (CountUsablePartyMons(battlerAtk) == 0 || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) @@ -4057,11 +4063,11 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_TOXIC_ORB: - if (!ShouldPoisonSelf(battlerAtk, aiData->abilities[battlerAtk])) + if (!ShouldPoison(battlerAtk, battlerAtk)) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_FLAME_ORB: - if (!ShouldBurnSelf(battlerAtk, aiData->abilities[battlerAtk]) && CanBeBurned(battlerAtk, aiData->abilities[battlerDef])) + if (!ShouldBurn(battlerAtk, battlerAtk) && CanBeBurned(battlerAtk, aiData->abilities[battlerDef])) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_BLACK_SLUDGE: @@ -4107,11 +4113,11 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) case HOLD_EFFECT_CHOICE_BAND: break; case HOLD_EFFECT_TOXIC_ORB: - if (ShouldPoisonSelf(battlerAtk, aiData->abilities[battlerAtk])) + if (ShouldPoison(battlerAtk, battlerAtk)) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_FLAME_ORB: - if (ShouldBurnSelf(battlerAtk, aiData->abilities[battlerAtk])) + if (ShouldBurn(battlerAtk, battlerAtk)) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_BLACK_SLUDGE: @@ -4761,11 +4767,11 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_TOXIC_ORB: - if (ShouldPoisonSelf(battlerAtk, aiData->abilities[battlerAtk])) + if (ShouldPoison(battlerAtk, battlerAtk)) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_FLAME_ORB: - if (ShouldBurnSelf(battlerAtk, aiData->abilities[battlerAtk])) + if (ShouldBurn(battlerAtk, battlerAtk)) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_BLACK_SLUDGE: diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index a43afbacda..a15d8df6c6 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -3118,21 +3118,110 @@ bool32 AI_CanPutToSleep(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move return TRUE; } -bool32 ShouldPoisonSelf(u32 battler, u32 ability) +static inline bool32 DoesBattlerBenefitFromAllVolatileStatus(u32 battler, u32 ability) { - if (CanBePoisoned(battler, battler, GetBattlerAbility(battler)) && ( - ability == ABILITY_MARVEL_SCALE - || ability == ABILITY_POISON_HEAL - || ability == ABILITY_QUICK_FEET - || ability == ABILITY_MAGIC_GUARD - || (ability == ABILITY_TOXIC_BOOST && HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL)) - || (ability == ABILITY_GUTS && HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL)) - || HasMoveEffect(battler, EFFECT_FACADE) - || HasMoveEffect(battler, EFFECT_PSYCHO_SHIFT))) - return TRUE; // battler can be poisoned and has move/ability that synergizes with being poisoned + if (ability == ABILITY_MARVEL_SCALE + || ability == ABILITY_QUICK_FEET + || ability == ABILITY_MAGIC_GUARD + || (ability == ABILITY_GUTS && HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL)) + || HasMoveEffect(battler, EFFECT_FACADE) + || HasMoveEffect(battler, EFFECT_PSYCHO_SHIFT)) + return TRUE; return FALSE; } +bool32 ShouldPoison(u32 battlerAtk, u32 battlerDef) +{ + u32 defAbility = GetBattlerAbility(battlerDef); + // Battler can be poisoned and has move/ability that synergizes with being poisoned + if (CanBePoisoned(battlerAtk, battlerDef, GetBattlerAbility(battlerDef)) && ( + DoesBattlerBenefitFromAllVolatileStatus(battlerDef, defAbility) + || defAbility == ABILITY_POISON_HEAL + || (defAbility == ABILITY_TOXIC_BOOST && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)))) + { + if (battlerAtk == battlerDef) // Targeting self + return TRUE; + else + return FALSE; + } + if (battlerAtk == battlerDef) + return FALSE; + else + return TRUE; +} + +bool32 ShouldBurn(u32 battlerAtk, u32 battlerDef) +{ + u32 defAbility = GetBattlerAbility(battlerDef); + // Battler can be burned and has move/ability that synergizes with being burned + if (CanBeBurned(battlerDef, defAbility) && ( + DoesBattlerBenefitFromAllVolatileStatus(battlerDef, defAbility) + || defAbility == ABILITY_HEATPROOF + || (defAbility == ABILITY_FLARE_BOOST && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)))) + { + if (battlerAtk == battlerDef) // Targeting self + return TRUE; + else + return FALSE; + } + + if (battlerAtk == battlerDef) + return FALSE; + else + return TRUE; +} + +bool32 ShouldFreezeOrFrostbite(u32 battlerAtk, u32 battlerDef) +{ + if (!B_USE_FROSTBITE) + { + if (CanBeFrozen(battlerDef)) + { + if (battlerAtk == battlerDef) // Targeting self + return FALSE; + else + return TRUE; + } + return FALSE; + } + else + { + u32 defAbility = GetBattlerAbility(battlerDef); + // Battler can be frostbitten and has move/ability that synergizes with being frostbitten + if (CanBeFrozen(battlerDef) && + DoesBattlerBenefitFromAllVolatileStatus(battlerDef, defAbility)) + { + if (battlerAtk == battlerDef) // Targeting self + return TRUE; + else + return FALSE; + } + + if (battlerAtk == battlerDef) + return FALSE; + else + return TRUE; + } +} + +bool32 ShouldParalyze(u32 battlerAtk, u32 battlerDef) +{ + u32 defAbility = GetBattlerAbility(battlerDef); + // Battler can be paralyzed and has move/ability that synergizes with being paralyzed + if (CanBeParalyzed(battlerDef, defAbility) && ( + DoesBattlerBenefitFromAllVolatileStatus(battlerDef, defAbility))) + { + if (battlerAtk == battlerDef) // Targeting self + return TRUE; + else + return FALSE; + } + if (battlerAtk == battlerDef) + return FALSE; + else + return TRUE; +} + bool32 AI_CanPoison(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 partnerMove) { if (!CanBePoisoned(battlerAtk, battlerDef, GetBattlerAbility(battlerDef)) @@ -3196,20 +3285,6 @@ bool32 AI_CanGetFrostbite(u32 battler, u32 ability) return TRUE; } -bool32 ShouldBurnSelf(u32 battler, u32 ability) -{ - if (CanBeBurned(battler, ability) && ( - ability == ABILITY_QUICK_FEET - || ability == ABILITY_HEATPROOF - || ability == ABILITY_MAGIC_GUARD - || (ability == ABILITY_FLARE_BOOST && HasMoveWithCategory(battler, DAMAGE_CATEGORY_SPECIAL)) - || (ability == ABILITY_GUTS && HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL)) - || HasMoveEffect(battler, EFFECT_FACADE) - || HasMoveEffect(battler, EFFECT_PSYCHO_SHIFT))) - return TRUE; - return FALSE; -} - bool32 AI_CanBurn(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove) { if (!CanBeBurned(battlerDef, defAbility) diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index 4eb3071b76..914c2818b2 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -911,3 +911,21 @@ AI_SINGLE_BATTLE_TEST("AI won't boost stats against opponent with Unaware") TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_BODY_SLAM); } } } + +AI_SINGLE_BATTLE_TEST("AI won't use status moves against opponents that would benefit") +{ + u32 aiMove; + PARAMETRIZE { aiMove = MOVE_WILL_O_WISP; } + PARAMETRIZE { aiMove = MOVE_TOXIC; } + PARAMETRIZE { aiMove = MOVE_THUNDER_WAVE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_WILL_O_WISP); + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_TOXIC); + ASSUME(GetMoveEffect(MOVE_THUNDER_WAVE) == EFFECT_PARALYZE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_SWELLOW) { Ability(ABILITY_GUTS); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, aiMove); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_TACKLE); } + } +}