Merge pull request #1875 from BuffelSaft/fix_ai_status_logic2

Fix AI status logic and CanAttackerFaintTarget
This commit is contained in:
ghoulslash 2021-11-08 10:34:10 -05:00 committed by GitHub
commit 4f56d0a2b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 37 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);
bool32 DoesBattlerIgnoreAbilityChecks(u16 atkAbility, u16 move);
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_IsBattlerGrounded(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
{
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)
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)
score -= 10;
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;
break;
case EFFECT_SPITE:
@ -2465,7 +2465,7 @@ static s16 AI_TryToFaint(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (gBattleMoves[move].power == 0)
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
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)
&& HasMoveWithSplit(battlerAtkPartner, SPLIT_PHYSICAL)
&& 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);
}
@ -2716,7 +2716,7 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (!IS_MOVE_STATUS(move)
&& (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG)
&& 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);
}
@ -2782,7 +2782,7 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
&& !IS_MOVE_STATUS(move)
&& HasMoveWithSplit(battlerAtkPartner, SPLIT_PHYSICAL)
&& 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);
}
@ -2971,7 +2971,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case ABILITY_BEAST_BOOST:
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
}
break;
@ -3772,7 +3772,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_FELL_STINGER:
if (gBattleMons[battlerAtk].statStages[STAT_ATK] < MAX_STAT_STAGE
&& 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
score += 9;
@ -4923,7 +4923,7 @@ static s16 AI_HPAware(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
}
// consider target HP
if (CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
{
score += 2;
}

View file

@ -660,9 +660,9 @@ bool32 MovesWithSplitUnusable(u32 attacker, u32 target, u32 split)
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moves[i] != MOVE_NONE
&& moves[i] != 0xFFFF
&& GetBattleMoveSplit(moves[i]) == split
&& !(unusable & gBitTable[i]))
&& moves[i] != 0xFFFF
&& GetBattleMoveSplit(moves[i]) == split
&& !(unusable & gBitTable[i]))
{
SetTypeBeforeUsingMove(moves[i], attacker);
GET_MOVE_TYPE(moves[i], moveType);
@ -1033,7 +1033,7 @@ bool32 IsAiFaster(u8 battler)
bool32 CanTargetFaintAi(u8 battlerDef, u8 battlerAtk)
{
s32 i, dmg;
u32 unusable = CheckMoveLimitations(battlerDef, 0, 0xFF & ~MOVE_LIMITATION_PP);
u32 unusable = CheckMoveLimitations(battlerDef, 0, 0xFF);
u16 *moves = gBattleResources->battleHistory->usedMoves[battlerDef];
for (i = 0; i < MAX_MON_MOVES; i++)
@ -1048,10 +1048,36 @@ bool32 CanTargetFaintAi(u8 battlerDef, u8 battlerAtk)
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 moveLimitations = CheckMoveLimitations(battlerAtk, 0, 0xFF);
u16 *moves = gBattleMons[battlerAtk].moves;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moves[i] != MOVE_NONE && moves[i] != 0xFFFF && !(moveLimitations & gBitTable[i]))
{
// 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)
{
s32 i, dmg;
u32 unusable = CheckMoveLimitations(battlerDef, 0, 0xFF & ~MOVE_LIMITATION_PP);
u32 unusable = CheckMoveLimitations(battlerDef, 0, 0xFF);
if (move != MOVE_NONE && move != 0xFFFF && !(unusable & gBitTable[i]) && AI_CalcDamage(move, battlerDef, battlerAtk) >= gBattleMons[battlerAtk].hp)
return TRUE;
@ -1063,7 +1089,7 @@ bool32 CanMoveFaintBattler(u16 move, u8 battlerDef, u8 battlerAtk, u8 nHits)
bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgMod)
{
u32 i;
u32 unusable = CheckMoveLimitations(battlerDef, 0, 0xFF & ~MOVE_LIMITATION_PP);
u32 unusable = CheckMoveLimitations(battlerDef, 0, 0xFF);
u16 *moves = gBattleResources->battleHistory->usedMoves[battlerDef];
for (i = 0; i < MAX_MON_MOVES; i++)
@ -1677,7 +1703,7 @@ u32 CountNegativeStatStages(u8 battlerId)
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.
if (gBattleMons[battlerDef].statStages[STAT_ATK] > 4
@ -1685,7 +1711,7 @@ bool32 ShouldLowerAttack(u8 battlerAtk, u8 battlerDef, u16 defAbility)
&& defAbility != ABILITY_CONTRARY
&& defAbility != ABILITY_CLEAR_BODY
&& defAbility != ABILITY_WHITE_SMOKE
//&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_HYPER_CUTTER)
return TRUE;
return FALSE;
@ -1693,7 +1719,7 @@ bool32 ShouldLowerAttack(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.
if (gBattleMons[battlerDef].statStages[STAT_DEF] > 4
@ -1701,7 +1727,7 @@ bool32 ShouldLowerDefense(u8 battlerAtk, u8 battlerDef, u16 defAbility)
&& defAbility != ABILITY_CONTRARY
&& defAbility != ABILITY_CLEAR_BODY
&& defAbility != ABILITY_WHITE_SMOKE
//&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_BIG_PECKS)
return TRUE;
return FALSE;
@ -1709,13 +1735,13 @@ bool32 ShouldLowerDefense(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.
if (IsAiFaster(AI_CHECK_SLOWER)
&& defAbility != ABILITY_CONTRARY
&& defAbility != ABILITY_CLEAR_BODY
//&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_WHITE_SMOKE)
return TRUE;
return FALSE;
@ -1723,14 +1749,14 @@ bool32 ShouldLowerSpeed(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.
if (gBattleMons[battlerDef].statStages[STAT_SPATK] > 4
&& HasMoveWithSplit(battlerDef, SPLIT_SPECIAL)
&& defAbility != ABILITY_CONTRARY
&& defAbility != ABILITY_CLEAR_BODY
//&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_WHITE_SMOKE)
return TRUE;
return FALSE;
@ -1738,14 +1764,14 @@ bool32 ShouldLowerSpAtk(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.
if (gBattleMons[battlerDef].statStages[STAT_SPDEF] > 4
&& HasMoveWithSplit(battlerAtk, SPLIT_SPECIAL)
&& defAbility != ABILITY_CONTRARY
&& defAbility != ABILITY_CLEAR_BODY
//&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_WHITE_SMOKE)
return TRUE;
return FALSE;
@ -1753,13 +1779,13 @@ bool32 ShouldLowerSpDef(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.
if (defAbility != ABILITY_CONTRARY
&& defAbility != ABILITY_CLEAR_BODY
&& defAbility != ABILITY_WHITE_SMOKE
//&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_KEEN_EYE)
return TRUE;
return FALSE;
@ -1767,19 +1793,19 @@ bool32 ShouldLowerAccuracy(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.
if (gBattleMons[battlerDef].statStages[STAT_EVASION] > DEFAULT_STAT_STAGE
&& defAbility != ABILITY_CONTRARY
&& defAbility != ABILITY_CLEAR_BODY
//&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_WHITE_SMOKE)
return TRUE;
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];
@ -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 (!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)
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
{
if (CanAttackerFaintTarget(battlerAtk, battlerDef, moveIndex, 0))
if (CanAIFaintTarget(battlerAtk, battlerDef, 0))
{
if (!BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->atkAbility))
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
{
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
&& !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;
}
}
else if (CanAttackerFaintTarget(battlerAtk, battlerDef, moveIndex, 2))
else if (CanAIFaintTarget(battlerAtk, battlerDef, 2))
{
// can knock out foe in 2 hits
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
&& 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
else
return FALSE; //Not as good to use move if you'll faint and not win