Improved Guaranteed Flinch logic (fake out et al) (#7501)

This commit is contained in:
surskitty 2025-08-06 17:39:43 -04:00 committed by GitHub
parent 13a8f655b5
commit e778d3cdfe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 76 additions and 13 deletions

View File

@ -197,7 +197,8 @@ bool32 IsHealingMove(u32 move);
bool32 HasHealingEffect(u32 battler);
bool32 IsTrappingMove(u32 move);
bool32 HasTrappingMoveEffect(u32 battler);
bool32 ShouldFakeOut(u32 battlerAtk, u32 battlerDef, u32 move);
bool32 IsFlinchGuaranteed(u32 battlerAtk, u32 battlerDef, u32 move);
bool32 HasChoiceEffect(u32 battler);
bool32 HasThawingMove(u32 battler);
bool32 IsStatRaisingEffect(enum BattleMoveEffects effect);
bool32 IsStatLoweringEffect(enum BattleMoveEffects effect);

View File

@ -1138,6 +1138,12 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (DoesBattlerIgnoreAbilityChecks(battlerAtk, abilityAtk, move))
abilityDef = ABILITY_NONE;
// If a pokemon can be guaranteed flinched, don't target the pokemon that can't be flinched.
if (hasTwoOpponents
&& !IsFlinchGuaranteed(battlerAtk, battlerDef, move) && IsFlinchGuaranteed(battlerAtk, BATTLE_PARTNER(battlerDef), move)
&& aiData->effectiveness[battlerAtk][BATTLE_PARTNER(battlerDef)][gAiThinkingStruct->movesetIndex] != UQ_4_12(0.0))
ADJUST_SCORE(-5);
// check non-user target
if (!(moveTarget & MOVE_TARGET_USER))
{
@ -1999,6 +2005,8 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_FIRST_TURN_ONLY:
if (!gDisableStructs[battlerAtk].isFirstTurn)
ADJUST_SCORE(-10);
if (HasChoiceEffect(battlerAtk))
ADJUST_SCORE(-5);
break;
case EFFECT_STOCKPILE:
if (gDisableStructs[battlerAtk].stockpileCounter >= 3)
@ -3946,6 +3954,10 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
ADJUST_SCORE(-20); // Force switch if all your attacking moves are physical and you have Natural Cure.
}
// check guaranteed flinch, a la Fake Out
if (IsFlinchGuaranteed(battlerAtk, battlerDef, move))
ADJUST_SCORE(BEST_EFFECT);
// Non-volatile statuses
switch(GetMoveNonVolatileStatus(move))
{
@ -4551,9 +4563,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
ADJUST_SCORE(IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF));
break;
case EFFECT_FIRST_TURN_ONLY:
if (ShouldFakeOut(battlerAtk, battlerDef, move) && MoveHasAdditionalEffectWithChance(move, MOVE_EFFECT_FLINCH, 100))
ADJUST_SCORE(GOOD_EFFECT);
else if (gDisableStructs[battlerAtk].isFirstTurn && GetBestDmgMoveFromBattler(battlerAtk, battlerDef, AI_ATTACKING) == move)
if (gDisableStructs[battlerAtk].isFirstTurn && GetBestDmgMoveFromBattler(battlerAtk, battlerDef, AI_ATTACKING) == move)
ADJUST_SCORE(BEST_EFFECT);
break;
case EFFECT_STOCKPILE:

View File

@ -3502,18 +3502,57 @@ bool32 ShouldTrap(u32 battlerAtk, u32 battlerDef, u32 move)
return FALSE;
}
bool32 ShouldFakeOut(u32 battlerAtk, u32 battlerDef, u32 move)
bool32 IsFlinchGuaranteed(u32 battlerAtk, u32 battlerDef, u32 move)
{
if ((!gDisableStructs[battlerAtk].isFirstTurn && MoveHasAdditionalEffectWithChance(move, MOVE_EFFECT_FLINCH, 100))
|| gAiLogicData->abilities[battlerAtk] == ABILITY_GORILLA_TACTICS
|| gAiLogicData->holdEffects[battlerAtk] == HOLD_EFFECT_CHOICE_BAND
|| gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK
|| DoesSubstituteBlockMove(battlerAtk, battlerDef, move)
|| (!IsMoldBreakerTypeAbility(battlerAtk, gAiLogicData->abilities[battlerAtk])
&& (gAiLogicData->abilities[battlerDef] == ABILITY_SHIELD_DUST || gAiLogicData->abilities[battlerDef] == ABILITY_INNER_FOCUS)))
if (!MoveHasAdditionalEffect(move, MOVE_EFFECT_FLINCH))
return FALSE;
return TRUE;
if (AI_IsSlower(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY))
return FALSE;
u32 i;
u32 additionalEffectCount = GetMoveAdditionalEffectCount(move);
// check move additional effects that are likely to happen
for (i = 0; i < additionalEffectCount; i++)
{
const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(move, i);
// Only consider effects with a guaranteed chance to happen
if (!MoveEffectIsGuaranteed(battlerAtk, gAiLogicData->abilities[battlerAtk], additionalEffect))
continue;
if (additionalEffect->moveEffect == MOVE_EFFECT_FLINCH)
{
if (gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK
|| DoesSubstituteBlockMove(battlerAtk, battlerDef, move)
|| (!IsMoldBreakerTypeAbility(battlerAtk, gAiLogicData->abilities[battlerAtk])
&& (gAiLogicData->abilities[battlerDef] == ABILITY_SHIELD_DUST || gAiLogicData->abilities[battlerDef] == ABILITY_INNER_FOCUS)))
return FALSE;
else
return TRUE;
}
}
return FALSE;
}
bool32 HasChoiceEffect(u32 battler)
{
u32 ability = gAiLogicData->abilities[battler];
if (ability == ABILITY_GORILLA_TACTICS)
return TRUE;
if (ability == ABILITY_KLUTZ)
return FALSE;
enum ItemHoldEffect holdEffect = gAiLogicData->holdEffects[battler];
switch (holdEffect)
{
case HOLD_EFFECT_CHOICE_BAND:
case HOLD_EFFECT_CHOICE_SCARF:
case HOLD_EFFECT_CHOICE_SPECS:
return TRUE;
default:
return FALSE;
}
}
static u32 FindMoveUsedXTurnsAgo(u32 battlerId, u32 x)

View File

@ -648,3 +648,16 @@ AI_DOUBLE_BATTLE_TEST("AI uses Power Split to improve its stats")
}
}
AI_DOUBLE_BATTLE_TEST("AI prefers to Fake Out the opponent vulnerable to flinching.")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_ZUBAT) { Ability(ABILITY_INNER_FOCUS); }
PLAYER(SPECIES_BRAIXEN) { Ability(ABILITY_BLAZE); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_FAKE_OUT, MOVE_BRANCH_POKE, MOVE_ROCK_SMASH); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { EXPECT_MOVE(opponentLeft, MOVE_FAKE_OUT, target:playerRight); }
}
}