Fix CanAIFaintTarget

Rename CanAIFaintTarget to CanIndexMoveFaintTarget and create a new function, CanAIFaintTarget, that checks if any known move can KO the target.
This commit is contained in:
BuffelSaft 2021-11-08 20:37:28 +13:00
parent 3a113e33b2
commit d3a845d511
3 changed files with 51 additions and 24 deletions

View File

@ -35,7 +35,8 @@ u16 AI_GetHoldEffect(u32 battlerId);
u32 AI_GetMoveAccuracy(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbility, u8 atkHoldEffect, u8 defHoldEffect, u16 move); u32 AI_GetMoveAccuracy(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbility, u8 atkHoldEffect, u8 defHoldEffect, u16 move);
bool32 DoesBattlerIgnoreAbilityChecks(u16 atkAbility, u16 move); bool32 DoesBattlerIgnoreAbilityChecks(u16 atkAbility, u16 move);
bool32 AI_WeatherHasEffect(void); bool32 AI_WeatherHasEffect(void);
bool32 CanAttackerFaintTarget(u8 battlerAtk, u8 battlerDef, u8 index, u8 numHits); bool32 CanAIFaintTarget(u8 battlerAtk, u8 battlerDef, u8 numHits);
bool32 CanIndexMoveFaintTarget(u8 battlerAtk, u8 battlerDef, u8 index, u8 numHits);
bool32 AI_IsTerrainAffected(u8 battlerId, u32 flags); bool32 AI_IsTerrainAffected(u8 battlerId, u32 flags);
bool32 AI_IsBattlerGrounded(u8 battlerId); bool32 AI_IsBattlerGrounded(u8 battlerId);
bool32 HasDamagingMove(u8 battlerId); bool32 HasDamagingMove(u8 battlerId);

View File

@ -1473,7 +1473,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
else if (move == MOVE_FAKE_OUT) // filter out first impression else if (move == MOVE_FAKE_OUT) // filter out first impression
{ {
if ((AI_DATA->atkHoldEffect == HOLD_EFFECT_CHOICE_BAND || AI_DATA->atkAbility == ABILITY_GORILLA_TACTICS) if ((AI_DATA->atkHoldEffect == HOLD_EFFECT_CHOICE_BAND || AI_DATA->atkAbility == ABILITY_GORILLA_TACTICS)
&& (CountUsablePartyMons(battlerDef) > 0 || !CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))) && (CountUsablePartyMons(battlerDef) > 0 || !CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)))
{ {
if (CountUsablePartyMons(battlerAtk) == 0) if (CountUsablePartyMons(battlerAtk) == 0)
score -= 10; // Don't lock the attacker into Fake Out if they can't switch out afterwards. score -= 10; // Don't lock the attacker into Fake Out if they can't switch out afterwards.
@ -1714,7 +1714,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (AI_DATA->defAbility == ABILITY_WONDER_GUARD && effectiveness < AI_EFFECTIVENESS_x2) if (AI_DATA->defAbility == ABILITY_WONDER_GUARD && effectiveness < AI_EFFECTIVENESS_x2)
score -= 10; score -= 10;
else if (AI_DATA->atkAbility != ABILITY_TRUANT else if (AI_DATA->atkAbility != ABILITY_TRUANT
&& !CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) && !CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
score -= 2; score -= 2;
break; break;
case EFFECT_SPITE: case EFFECT_SPITE:
@ -2465,7 +2465,7 @@ static s16 AI_TryToFaint(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (gBattleMoves[move].power == 0) if (gBattleMoves[move].power == 0)
return score; // can't make anything faint with no power return score; // can't make anything faint with no power
if (CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0) && gBattleMoves[move].effect != EFFECT_EXPLOSION) if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0) && gBattleMoves[move].effect != EFFECT_EXPLOSION)
{ {
// this move can faint the target // this move can faint the target
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 || GetMovePriority(battlerAtk, move) > 0) if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 || GetMovePriority(battlerAtk, move) > 0)
@ -2707,7 +2707,7 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
&& !IS_MOVE_STATUS(move) && !IS_MOVE_STATUS(move)
&& HasMoveWithSplit(battlerAtkPartner, SPLIT_PHYSICAL) && HasMoveWithSplit(battlerAtkPartner, SPLIT_PHYSICAL)
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK) && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)
&& !CanAttackerFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 1)) && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 1))
{ {
RETURN_SCORE_PLUS(1); RETURN_SCORE_PLUS(1);
} }
@ -2716,7 +2716,7 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (!IS_MOVE_STATUS(move) if (!IS_MOVE_STATUS(move)
&& (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG) && (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG)
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPEED) && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPEED)
&& !CanAttackerFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 1)) && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 1))
{ {
RETURN_SCORE_PLUS(1); RETURN_SCORE_PLUS(1);
} }
@ -2782,7 +2782,7 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
&& !IS_MOVE_STATUS(move) && !IS_MOVE_STATUS(move)
&& HasMoveWithSplit(battlerAtkPartner, SPLIT_PHYSICAL) && HasMoveWithSplit(battlerAtkPartner, SPLIT_PHYSICAL)
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK) && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)
&& !CanAttackerFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 0)) && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 0))
{ {
RETURN_SCORE_PLUS(1); RETURN_SCORE_PLUS(1);
} }
@ -2971,7 +2971,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case ABILITY_BEAST_BOOST: case ABILITY_BEAST_BOOST:
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // attacker should go first if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // attacker should go first
{ {
if (CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
score += 8; // prioritize killing target for stat boost score += 8; // prioritize killing target for stat boost
} }
break; break;
@ -3772,7 +3772,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_FELL_STINGER: case EFFECT_FELL_STINGER:
if (gBattleMons[battlerAtk].statStages[STAT_ATK] < MAX_STAT_STAGE if (gBattleMons[battlerAtk].statStages[STAT_ATK] < MAX_STAT_STAGE
&& AI_DATA->atkAbility != ABILITY_CONTRARY && AI_DATA->atkAbility != ABILITY_CONTRARY
&& CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) && CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
{ {
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // Attacker goes first if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // Attacker goes first
score += 9; score += 9;
@ -4923,7 +4923,7 @@ static s16 AI_HPAware(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
} }
// consider target HP // consider target HP
if (CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
{ {
score += 2; score += 2;
} }

View File

@ -1048,6 +1048,32 @@ bool32 CanTargetFaintAi(u8 battlerDef, u8 battlerAtk)
return FALSE; return FALSE;
} }
// Check if AI mon has the means to faint the target with any of its moves.
// If numHits > 1, check if the target will be KO'ed by that number of hits (ignoring healing effects)
bool32 CanAIFaintTarget(u8 battlerAtk, u8 battlerDef, u8 numHits)
{
s32 i, dmg;
u32 unusable = CheckMoveLimitations(battlerAtk, 0, 0xFF & ~MOVE_LIMITATION_PP);
u16 *moves = gBattleMons[battlerAtk].moves;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moves[i] != MOVE_NONE && moves[i] != 0xFFFF && !(unusable & gBitTable[i]))
{
continue;
}
// Use the pre-calculated value in simulatedDmg instead of re-calculating it
dmg = AI_THINKING_STRUCT->simulatedDmg[battlerAtk][battlerDef][i];
if (numHits)
dmg *= numHits;
if (gBattleMons[battlerDef].hp <= dmg)
return TRUE;
}
return FALSE;
}
bool32 CanMoveFaintBattler(u16 move, u8 battlerDef, u8 battlerAtk, u8 nHits) bool32 CanMoveFaintBattler(u16 move, u8 battlerDef, u8 battlerAtk, u8 nHits)
{ {
s32 i, dmg; s32 i, dmg;
@ -1677,7 +1703,7 @@ u32 CountNegativeStatStages(u8 battlerId)
bool32 ShouldLowerAttack(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerAttack(u8 battlerAtk, u8 battlerDef, u16 defAbility)
{ {
if (IsAiFaster(AI_CHECK_FASTER) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (IsAiFaster(AI_CHECK_FASTER) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return FALSE; // Don't bother lowering stats if can kill enemy. return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_ATK] > 4 if (gBattleMons[battlerDef].statStages[STAT_ATK] > 4
@ -1693,7 +1719,7 @@ bool32 ShouldLowerAttack(u8 battlerAtk, u8 battlerDef, u16 defAbility)
bool32 ShouldLowerDefense(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerDefense(u8 battlerAtk, u8 battlerDef, u16 defAbility)
{ {
if (IsAiFaster(AI_CHECK_FASTER) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (IsAiFaster(AI_CHECK_FASTER) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return FALSE; // Don't bother lowering stats if can kill enemy. return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_DEF] > 4 if (gBattleMons[battlerDef].statStages[STAT_DEF] > 4
@ -1709,7 +1735,7 @@ bool32 ShouldLowerDefense(u8 battlerAtk, u8 battlerDef, u16 defAbility)
bool32 ShouldLowerSpeed(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerSpeed(u8 battlerAtk, u8 battlerDef, u16 defAbility)
{ {
if (IsAiFaster(AI_CHECK_FASTER) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (IsAiFaster(AI_CHECK_FASTER) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return FALSE; // Don't bother lowering stats if can kill enemy. return FALSE; // Don't bother lowering stats if can kill enemy.
if (IsAiFaster(AI_CHECK_SLOWER) if (IsAiFaster(AI_CHECK_SLOWER)
@ -1723,7 +1749,7 @@ bool32 ShouldLowerSpeed(u8 battlerAtk, u8 battlerDef, u16 defAbility)
bool32 ShouldLowerSpAtk(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerSpAtk(u8 battlerAtk, u8 battlerDef, u16 defAbility)
{ {
if (IsAiFaster(AI_CHECK_FASTER) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (IsAiFaster(AI_CHECK_FASTER) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return FALSE; // Don't bother lowering stats if can kill enemy. return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_SPATK] > 4 if (gBattleMons[battlerDef].statStages[STAT_SPATK] > 4
@ -1738,7 +1764,7 @@ bool32 ShouldLowerSpAtk(u8 battlerAtk, u8 battlerDef, u16 defAbility)
bool32 ShouldLowerSpDef(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerSpDef(u8 battlerAtk, u8 battlerDef, u16 defAbility)
{ {
if (IsAiFaster(AI_CHECK_FASTER) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (IsAiFaster(AI_CHECK_FASTER) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return FALSE; // Don't bother lowering stats if can kill enemy. return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_SPDEF] > 4 if (gBattleMons[battlerDef].statStages[STAT_SPDEF] > 4
@ -1753,7 +1779,7 @@ bool32 ShouldLowerSpDef(u8 battlerAtk, u8 battlerDef, u16 defAbility)
bool32 ShouldLowerAccuracy(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerAccuracy(u8 battlerAtk, u8 battlerDef, u16 defAbility)
{ {
if (IsAiFaster(AI_CHECK_FASTER) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (IsAiFaster(AI_CHECK_FASTER) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return FALSE; // Don't bother lowering stats if can kill enemy. return FALSE; // Don't bother lowering stats if can kill enemy.
if (defAbility != ABILITY_CONTRARY if (defAbility != ABILITY_CONTRARY
@ -1767,7 +1793,7 @@ bool32 ShouldLowerAccuracy(u8 battlerAtk, u8 battlerDef, u16 defAbility)
bool32 ShouldLowerEvasion(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerEvasion(u8 battlerAtk, u8 battlerDef, u16 defAbility)
{ {
if (IsAiFaster(AI_CHECK_FASTER) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (IsAiFaster(AI_CHECK_FASTER) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return FALSE; // Don't bother lowering stats if can kill enemy. return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_EVASION] > DEFAULT_STAT_STAGE if (gBattleMons[battlerDef].statStages[STAT_EVASION] > DEFAULT_STAT_STAGE
@ -1779,7 +1805,7 @@ bool32 ShouldLowerEvasion(u8 battlerAtk, u8 battlerDef, u16 defAbility)
return FALSE; return FALSE;
} }
bool32 CanAttackerFaintTarget(u8 battlerAtk, u8 battlerDef, u8 index, u8 numHits) bool32 CanIndexMoveFaintTarget(u8 battlerAtk, u8 battlerDef, u8 index, u8 numHits)
{ {
s32 dmg = AI_THINKING_STRUCT->simulatedDmg[battlerAtk][battlerDef][index]; s32 dmg = AI_THINKING_STRUCT->simulatedDmg[battlerAtk][battlerDef][index];
@ -2433,9 +2459,9 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // Attacker goes first if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // Attacker goes first
{ {
if (!CanAttackerFaintTarget(battlerAtk, battlerDef, moveIndex, 0)) // Can't KO foe otherwise if (!CanAIFaintTarget(battlerAtk, battlerDef, 0)) // Can't KO foe otherwise
{ {
if (CanAttackerFaintTarget(battlerAtk, battlerDef, moveIndex, 2)) if (CanAIFaintTarget(battlerAtk, battlerDef, 2))
{ {
// attacker can kill target in two hits (theoretically) // attacker can kill target in two hits (theoretically)
if (CanTargetFaintAi(battlerDef, battlerAtk)) if (CanTargetFaintAi(battlerDef, battlerAtk))
@ -2500,7 +2526,7 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo
} }
else if (CanTargetFaintAiWithMod(battlerDef, battlerAtk, 0, 2)) // Foe can 2HKO AI else if (CanTargetFaintAiWithMod(battlerDef, battlerAtk, 0, 2)) // Foe can 2HKO AI
{ {
if (CanAttackerFaintTarget(battlerAtk, battlerDef, moveIndex, 0)) if (CanAIFaintTarget(battlerAtk, battlerDef, 0))
{ {
if (!BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->atkAbility)) if (!BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->atkAbility))
return CAN_TRY_PIVOT; // Use this move to KO if you must return CAN_TRY_PIVOT; // Use this move to KO if you must
@ -2512,7 +2538,7 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo
} }
else // Foe can 3HKO+ AI else // Foe can 3HKO+ AI
{ {
if (CanAttackerFaintTarget(battlerAtk, battlerDef, moveIndex, 0)) if (CanAIFaintTarget(battlerAtk, battlerDef, 0))
{ {
if (!BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->atkAbility) // This is the only move that can KO if (!BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->atkAbility) // This is the only move that can KO
&& !hasStatBoost) //You're not wasting a valuable stat boost && !hasStatBoost) //You're not wasting a valuable stat boost
@ -2520,7 +2546,7 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo
return CAN_TRY_PIVOT; return CAN_TRY_PIVOT;
} }
} }
else if (CanAttackerFaintTarget(battlerAtk, battlerDef, moveIndex, 2)) else if (CanAIFaintTarget(battlerAtk, battlerDef, 2))
{ {
// can knock out foe in 2 hits // can knock out foe in 2 hits
if (IS_MOVE_STATUS(move) && (shouldSwitch //Damaging move if (IS_MOVE_STATUS(move) && (shouldSwitch //Damaging move
@ -2904,7 +2930,7 @@ bool32 ShouldUseRecoilMove(u8 battlerAtk, u8 battlerDef, u32 recoilDmg, u8 moveI
if (recoilDmg >= gBattleMons[battlerAtk].hp //Recoil kills attacker if (recoilDmg >= gBattleMons[battlerAtk].hp //Recoil kills attacker
&& CountUsablePartyMons(battlerDef) != 0) //Foe has more than 1 target left && CountUsablePartyMons(battlerDef) != 0) //Foe has more than 1 target left
{ {
if (recoilDmg >= gBattleMons[battlerDef].hp && !CanAttackerFaintTarget(battlerAtk, battlerDef, moveIndex, 0)) if (recoilDmg >= gBattleMons[battlerDef].hp && !CanAIFaintTarget(battlerAtk, battlerDef, 0))
return TRUE; //If it's the only KO move then just use it return TRUE; //If it's the only KO move then just use it
else else
return FALSE; //Not as good to use move if you'll faint and not win return FALSE; //Not as good to use move if you'll faint and not win