move ai score calc to STATE_TURN_START_RECORD so AI can consider move selection for item use

This commit is contained in:
ghoulslash 2022-06-05 11:09:04 -04:00
parent c694e0cb90
commit b33a8b0cb4
10 changed files with 187 additions and 104 deletions

View File

@ -584,6 +584,8 @@ struct BattleStruct
bool8 spriteIgnore0Hp; bool8 spriteIgnore0Hp;
struct Illusion illusion[MAX_BATTLERS_COUNT]; struct Illusion illusion[MAX_BATTLERS_COUNT];
s8 aiFinalScore[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // AI, target, moves to make debugging easier s8 aiFinalScore[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // AI, target, moves to make debugging easier
u8 aiMoveOrAction[MAX_BATTLERS_COUNT];
u8 aiChosenTarget[MAX_BATTLERS_COUNT];
u8 soulheartBattlerId; u8 soulheartBattlerId;
u8 friskedBattler; // Frisk needs to identify 2 battlers in double battles. u8 friskedBattler; // Frisk needs to identify 2 battlers in double battles.
bool8 friskedAbility; // If identifies two mons, show the ability pop-up only once. bool8 friskedAbility; // If identifies two mons, show the ability pop-up only once.

View File

@ -19,7 +19,7 @@
return score; \ return score; \
} }
u8 ComputeBattleAiScores(u8 battler);
void BattleAI_SetupItems(void); void BattleAI_SetupItems(void);
void BattleAI_SetupFlags(void); void BattleAI_SetupFlags(void);
void BattleAI_SetupAIData(u8 defaultScoreMoves); void BattleAI_SetupAIData(u8 defaultScoreMoves);

View File

@ -20,6 +20,7 @@ void ClearBattlerItemEffectHistory(u8 battlerId);
void SaveBattlerData(u8 battlerId); void SaveBattlerData(u8 battlerId);
void SetBattlerData(u8 battlerId); void SetBattlerData(u8 battlerId);
void RestoreBattlerData(u8 battlerId); void RestoreBattlerData(u8 battlerId);
u16 GetAIChosenMove(u8 battlerId);
bool32 WillAIStrikeFirst(void); bool32 WillAIStrikeFirst(void);
u32 GetTotalBaseStat(u32 species); u32 GetTotalBaseStat(u32 species);
@ -27,7 +28,7 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler);
bool32 AtMaxHp(u8 battler); bool32 AtMaxHp(u8 battler);
u32 GetHealthPercentage(u8 battler); u32 GetHealthPercentage(u8 battler);
bool32 IsBattlerTrapped(u8 battler, bool8 switching); bool32 IsBattlerTrapped(u8 battler, bool8 switching);
u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2); u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2, u16 consideredMove);
bool32 CanTargetFaintAi(u8 battlerDef, u8 battlerAtk); bool32 CanTargetFaintAi(u8 battlerDef, u8 battlerAtk);
bool32 CanMoveFaintBattler(u16 move, u8 battlerDef, u8 battlerAtk, u8 nHits); bool32 CanMoveFaintBattler(u16 move, u8 battlerDef, u8 battlerAtk, u8 nHits);
bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgMod); bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgMod);

View File

@ -81,6 +81,8 @@
#define WILD_DOUBLE_BATTLE ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_TRAINER)))) #define WILD_DOUBLE_BATTLE ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_TRAINER))))
#define BATTLE_TWO_VS_ONE_OPPONENT ((gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && gTrainerBattleOpponent_B == 0xFFFF)) #define BATTLE_TWO_VS_ONE_OPPONENT ((gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && gTrainerBattleOpponent_B == 0xFFFF))
#define BATTLE_TYPE_HAS_AI (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER)
// Battle Outcome defines // Battle Outcome defines
#define B_OUTCOME_WON 1 #define B_OUTCOME_WON 1

View File

@ -174,11 +174,12 @@ void BattleAI_SetupFlags(void)
AI_THINKING_STRUCT->aiFlags |= AI_FLAG_DOUBLE_BATTLE; // Act smart in doubles and don't attack your partner. AI_THINKING_STRUCT->aiFlags |= AI_FLAG_DOUBLE_BATTLE; // Act smart in doubles and don't attack your partner.
} }
// sBattler_AI set in ComputeBattleAiScores
void BattleAI_SetupAIData(u8 defaultScoreMoves) void BattleAI_SetupAIData(u8 defaultScoreMoves)
{ {
s32 i, move, dmg; s32 i, move, dmg;
u8 moveLimitations; u8 moveLimitations;
// Clear AI data but preserve the flags. // Clear AI data but preserve the flags.
u32 flags = AI_THINKING_STRUCT->aiFlags; u32 flags = AI_THINKING_STRUCT->aiFlags;
memset(AI_THINKING_STRUCT, 0, sizeof(struct AI_ThinkingStruct)); memset(AI_THINKING_STRUCT, 0, sizeof(struct AI_ThinkingStruct));
@ -204,8 +205,9 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves)
AI_THINKING_STRUCT->score[i] = 0; AI_THINKING_STRUCT->score[i] = 0;
} }
sBattler_AI = gActiveBattler; //sBattler_AI = gActiveBattler;
gBattlerTarget = SetRandomTarget(sBattler_AI); gBattlerTarget = SetRandomTarget(sBattler_AI);
gBattleStruct->aiChosenTarget[sBattler_AI] = gBattlerTarget;
} }
u8 BattleAI_ChooseMoveOrAction(void) u8 BattleAI_ChooseMoveOrAction(void)
@ -226,53 +228,73 @@ u8 BattleAI_ChooseMoveOrAction(void)
return ret; return ret;
} }
// damages/other info computed in GetAIDataAndCalcDmg
u8 ComputeBattleAiScores(u8 battler)
{
sBattler_AI = battler;
BattleAI_SetupAIData(0xF);
return BattleAI_ChooseMoveOrAction();
}
static void SetBattlerAiData(u8 battlerId)
{
AI_DATA->abilities[battlerId] = AI_GetAbility(battlerId);
AI_DATA->items[battlerId] = gBattleMons[battlerId].item;
AI_DATA->holdEffects[battlerId] = AI_GetHoldEffect(battlerId);
AI_DATA->holdEffectParams[battlerId] = GetBattlerHoldEffectParam(battlerId);
AI_DATA->predictedMoves[battlerId] = gLastMoves[battlerId];
AI_DATA->hpPercents[battlerId] = GetHealthPercentage(battlerId);
AI_DATA->moveLimitations[battlerId] = CheckMoveLimitations(battlerId, 0, 0xFF);
}
void GetAiLogicData(void) void GetAiLogicData(void)
{ {
u32 battlerAtk, battlerDef, i, move; u32 battlerAtk, battlerDef, i, move;
u8 effectiveness; u8 effectiveness;
s32 dmg; s32 dmg;
memset(AI_DATA, 0, sizeof(struct AiLogicData));
if (!(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER) if (!(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER)
&& !IsWildMonSmart())) && !IsWildMonSmart()))
return; return;
memset(AI_DATA, 0, sizeof(struct AiLogicData)); // get/assume all battler data
for (battlerAtk = 0; battlerAtk < MAX_BATTLERS_COUNT; battlerAtk++) for (i = 0; i < gBattlersCount; i++)
{ {
if (IsBattlerAlive(battlerAtk)) { if (IsBattlerAlive(i)) {
AI_DATA->abilities[battlerAtk] = AI_GetAbility(battlerAtk); SetBattlerAiData(i);
AI_DATA->items[battlerAtk] = gBattleMons[battlerAtk].item; }
AI_DATA->holdEffects[battlerAtk] = AI_GetHoldEffect(battlerAtk); }
AI_DATA->holdEffectParams[battlerAtk] = GetBattlerHoldEffectParam(battlerAtk);
AI_DATA->predictedMoves[battlerAtk] = gLastMoves[battlerAtk]; // simulate AI damage
AI_DATA->hpPercents[battlerAtk] = GetHealthPercentage(battlerAtk); for (battlerAtk = 0; battlerAtk < gBattlersCount; battlerAtk++)
AI_DATA->moveLimitations[battlerAtk] = CheckMoveLimitations(battlerAtk, 0, MOVE_LIMITATIONS_ALL); {
if (!IsBattlerAlive(battlerAtk)
|| !IsBattlerAIControlled(battlerAtk)) {
continue;
}
for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
if (battlerAtk == battlerDef)
continue;
// Simulate dmg for all AI moves against all other targets RecordKnownMove(battlerDef, gLastMoves[battlerDef]);
for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++) for (i = 0; i < MAX_MON_MOVES; i++)
{ {
if (battlerAtk == battlerDef) dmg = 0;
continue; effectiveness = AI_EFFECTIVENESS_x0;
move = gBattleMons[battlerAtk].moves[i];
RecordKnownMove(battlerDef, gLastMoves[battlerDef]); if (move != 0
for (i = 0; i < MAX_MON_MOVES; i++) && move != 0xFFFF
{ //&& gBattleMoves[move].power != 0 /* we want to get effectiveness of status moves */
dmg = 0; && !(AI_DATA->moveLimitations[battlerAtk] & gBitTable[i])) {
effectiveness = AI_EFFECTIVENESS_x0; dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness);
move = gBattleMons[battlerAtk].moves[i];
if (move != 0
&& move != 0xFFFF
&& gBattleMoves[move].power != 0
&& !(AI_DATA->moveLimitations[battlerAtk] & gBitTable[i]))
{
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness);
if (dmg == 0)
dmg = 1;
}
AI_DATA->simulatedDmg[battlerAtk][battlerDef][i] = dmg;
AI_DATA->effectiveness[battlerAtk][battlerDef][i] = effectiveness;
} }
AI_DATA->simulatedDmg[battlerAtk][battlerDef][i] = dmg;
AI_DATA->effectiveness[battlerAtk][battlerDef][i] = effectiveness;
} }
} }
} }
@ -495,6 +517,7 @@ static u8 ChooseMoveOrAction_Doubles(void)
} }
gBattlerTarget = mostViableTargetsArray[Random() % mostViableTargetsNo]; gBattlerTarget = mostViableTargetsArray[Random() % mostViableTargetsNo];
gBattleStruct->aiChosenTarget[sBattler_AI] = gBattlerTarget;
return actionOrMoveIndex[gBattlerTarget]; return actionOrMoveIndex[gBattlerTarget];
} }
@ -556,7 +579,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
u16 moveEffect = gBattleMoves[move].effect; u16 moveEffect = gBattleMoves[move].effect;
s32 moveType; s32 moveType;
u16 moveTarget = AI_GetBattlerMoveTargetType(battlerAtk, move); u16 moveTarget = AI_GetBattlerMoveTargetType(battlerAtk, move);
u16 accuracy = AI_GetMoveAccuracy(battlerAtk, battlerDef, AI_DATA->atkAbility, AI_DATA->defAbility, AI_DATA->atkHoldEffect, AI_DATA->defHoldEffect, move); u16 accuracy = AI_GetMoveAccuracy(battlerAtk, battlerDef, move);
u8 effectiveness = AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]; u8 effectiveness = AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex];
bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk); bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk);
u32 i; u32 i;
@ -593,7 +616,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
} }
// check off screen // check off screen
if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
RETURN_SCORE_MINUS(20); // if target off screen and we go first, don't use move RETURN_SCORE_MINUS(20); // if target off screen and we go first, don't use move
// check if negates type // check if negates type
@ -1292,7 +1315,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
&& (B_MENTAL_HERB >= GEN_5 && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB) && (B_MENTAL_HERB >= GEN_5 && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)
&& !PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, AI_DATA->partnerMove)) && !PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, AI_DATA->partnerMove))
{ {
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker should go first
{ {
if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF) if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF)
score -= 10; // no anticipated move to disable score -= 10; // no anticipated move to disable
@ -1312,7 +1335,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
&& (B_MENTAL_HERB >= GEN_5 && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB) && (B_MENTAL_HERB >= GEN_5 && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)
&& !DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, AI_DATA->partnerMove)) && !DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, AI_DATA->partnerMove))
{ {
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker should go first
{ {
if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF) if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF)
score -= 10; // no anticipated move to encore score -= 10; // no anticipated move to encore
@ -1755,7 +1778,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break; break;
case EFFECT_SPITE: case EFFECT_SPITE:
case EFFECT_MIMIC: case EFFECT_MIMIC:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker should go first
{ {
if (gLastMoves[battlerDef] == MOVE_NONE if (gLastMoves[battlerDef] == MOVE_NONE
|| gLastMoves[battlerDef] == 0xFFFF) || gLastMoves[battlerDef] == 0xFFFF)
@ -1912,7 +1935,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (isDoubleBattle) if (isDoubleBattle)
{ {
if (IsHazardMoveEffect(gBattleMoves[AI_DATA->partnerMove].effect) // partner is going to set up hazards if (IsHazardMoveEffect(gBattleMoves[AI_DATA->partnerMove].effect) // partner is going to set up hazards
&& AI_WhoStrikesFirst(BATTLE_PARTNER(battlerAtk), battlerAtk) == AI_IS_FASTER) // partner is going to set up before the potential Defog && AI_WhoStrikesFirst(BATTLE_PARTNER(battlerAtk), battlerAtk, AI_DATA->partnerMove) == AI_IS_FASTER) // partner is going to set up before the potential Defog
{ {
score -= 10; score -= 10;
break; // Don't use Defog if partner is going to set up hazards break; // Don't use Defog if partner is going to set up hazards
@ -1950,7 +1973,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break; break;
case EFFECT_SEMI_INVULNERABLE: case EFFECT_SEMI_INVULNERABLE:
if (predictedMove != MOVE_NONE if (predictedMove != MOVE_NONE
&& AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER
&& gBattleMoves[predictedMove].effect == EFFECT_SEMI_INVULNERABLE) && gBattleMoves[predictedMove].effect == EFFECT_SEMI_INVULNERABLE)
score -= 10; // Don't Fly/dig/etc if opponent is going to fly/dig/etc after you score -= 10; // Don't Fly/dig/etc if opponent is going to fly/dig/etc after you
@ -2131,7 +2154,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_ME_FIRST: case EFFECT_ME_FIRST:
if (predictedMove != MOVE_NONE) if (predictedMove != MOVE_NONE)
{ {
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER)
score -= 10; // Target is predicted to go first, Me First will fail score -= 10; // Target is predicted to go first, Me First will fail
else else
return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score); return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score);
@ -2320,7 +2343,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
} }
break; break;
case EFFECT_ELECTRIFY: case EFFECT_ELECTRIFY:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER
//|| GetMoveTypeSpecial(battlerDef, predictedMove) == TYPE_ELECTRIC // Move will already be electric type //|| GetMoveTypeSpecial(battlerDef, predictedMove) == TYPE_ELECTRIC // Move will already be electric type
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, AI_DATA->partnerMove)) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, AI_DATA->partnerMove))
score -= 10; score -= 10;
@ -2349,7 +2372,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_INSTRUCT: case EFFECT_INSTRUCT:
{ {
u16 instructedMove; u16 instructedMove;
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER)
instructedMove = predictedMove; instructedMove = predictedMove;
else else
instructedMove = gLastMoves[battlerDef]; instructedMove = gLastMoves[battlerDef];
@ -2389,21 +2412,21 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break; break;
case EFFECT_QUASH: case EFFECT_QUASH:
if (!isDoubleBattle if (!isDoubleBattle
|| AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, AI_DATA->partnerMove)) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, AI_DATA->partnerMove))
score -= 10; score -= 10;
break; break;
case EFFECT_AFTER_YOU: case EFFECT_AFTER_YOU:
if (!IsTargetingPartner(battlerAtk, battlerDef) if (!IsTargetingPartner(battlerAtk, battlerDef)
|| !isDoubleBattle || !isDoubleBattle
|| AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, AI_DATA->partnerMove)) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, AI_DATA->partnerMove))
score -= 10; score -= 10;
break; break;
case EFFECT_SUCKER_PUNCH: case EFFECT_SUCKER_PUNCH:
if (predictedMove != MOVE_NONE) if (predictedMove != MOVE_NONE)
{ {
if (IS_MOVE_STATUS(predictedMove) || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent going first if (IS_MOVE_STATUS(predictedMove) || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Opponent going first
score -= 10; score -= 10;
} }
break; break;
@ -2455,7 +2478,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score--; // don't want to move last score--; // don't want to move last
break; break;
case EFFECT_FLAIL: case EFFECT_FLAIL:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER // Opponent should go first if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER // Opponent should go first
|| AI_DATA->hpPercents[battlerAtk] > 50) || AI_DATA->hpPercents[battlerAtk] > 50)
score -= 4; score -= 4;
break; break;
@ -2604,7 +2627,7 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
// Ally decided to use Frost Breath on us. we must have Anger Point as our ability // Ally decided to use Frost Breath on us. we must have Anger Point as our ability
if (AI_DATA->abilities[battlerAtk] == ABILITY_ANGER_POINT) if (AI_DATA->abilities[battlerAtk] == ABILITY_ANGER_POINT)
{ {
if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner) == AI_IS_SLOWER) // Partner moving first if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner, move) == AI_IS_SLOWER) // Partner moving first
{ {
// discourage raising our attack since it's about to be maxed out // discourage raising our attack since it's about to be maxed out
if (IsAttackBoostMoveEffect(effect)) if (IsAttackBoostMoveEffect(effect))
@ -2871,7 +2894,7 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_INSTRUCT: case EFFECT_INSTRUCT:
{ {
u16 instructedMove; u16 instructedMove;
if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner) == AI_IS_FASTER) if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner, move) == AI_IS_FASTER)
instructedMove = AI_DATA->partnerMove; instructedMove = AI_DATA->partnerMove;
else else
instructedMove = gLastMoves[battlerAtkPartner]; instructedMove = gLastMoves[battlerAtkPartner];
@ -2885,8 +2908,8 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
} }
break; break;
case EFFECT_AFTER_YOU: case EFFECT_AFTER_YOU:
if (AI_WhoStrikesFirst(battlerAtkPartner, FOE(battlerAtkPartner) == AI_IS_SLOWER) // Opponent mon 1 goes before partner if (AI_WhoStrikesFirst(battlerAtkPartner, FOE(battlerAtkPartner), AI_DATA->partnerMove) == AI_IS_SLOWER // Opponent mon 1 goes before partner
|| AI_WhoStrikesFirst(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)) == AI_IS_SLOWER)) // Opponent mon 2 goes before partner || AI_WhoStrikesFirst(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)), AI_DATA->partnerMove) == AI_IS_SLOWER) // Opponent mon 2 goes before partner
{ {
if (gBattleMoves[AI_DATA->partnerMove].effect == EFFECT_COUNTER || gBattleMoves[AI_DATA->partnerMove].effect == EFFECT_MIRROR_COAT) if (gBattleMoves[AI_DATA->partnerMove].effect == EFFECT_COUNTER || gBattleMoves[AI_DATA->partnerMove].effect == EFFECT_MIRROR_COAT)
break; // These moves need to go last break; // These moves need to go last
@ -2913,7 +2936,7 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_MAGNITUDE: case EFFECT_MAGNITUDE:
if (!IsBattlerGrounded(battlerAtkPartner) if (!IsBattlerGrounded(battlerAtkPartner)
|| (IsBattlerGrounded(battlerAtkPartner) || (IsBattlerGrounded(battlerAtkPartner)
&& AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner) == AI_IS_SLOWER && AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner, move) == AI_IS_SLOWER
&& IsUngroundingEffect(gBattleMoves[AI_DATA->partnerMove].effect))) && IsUngroundingEffect(gBattleMoves[AI_DATA->partnerMove].effect)))
score += 2; score += 2;
else if (IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_FIRE) else if (IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_FIRE)
@ -2986,7 +3009,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
// check already dead // check already dead
if (!IsBattlerIncapacitated(battlerDef, AI_DATA->abilities[battlerDef]) if (!IsBattlerIncapacitated(battlerDef, AI_DATA->abilities[battlerDef])
&& CanTargetFaintAi(battlerAtk, battlerDef) && CanTargetFaintAi(battlerAtk, battlerDef)
&& AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent should go first && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Opponent should go first
{ {
if (atkPriority > 0) if (atkPriority > 0)
score++; score++;
@ -3034,7 +3057,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case ABILITY_GRIM_NEIGH: case ABILITY_GRIM_NEIGH:
case ABILITY_AS_ONE_ICE_RIDER: case ABILITY_AS_ONE_ICE_RIDER:
case ABILITY_AS_ONE_SHADOW_RIDER: case ABILITY_AS_ONE_SHADOW_RIDER:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker should go first
{ {
if (CanIndexMoveFaintTarget(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
@ -3481,7 +3504,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score++; score++;
break; break;
case EFFECT_MIMIC: case EFFECT_MIMIC:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
{ {
if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF) if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF)
return AI_CheckViability(battlerAtk, battlerDef, gLastMoves[battlerDef], score); return AI_CheckViability(battlerAtk, battlerDef, gLastMoves[battlerDef], score);
@ -3540,7 +3563,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (gDisableStructs[battlerDef].disableTimer == 0 if (gDisableStructs[battlerDef].disableTimer == 0
&& (B_MENTAL_HERB >= GEN_5 && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)) // mental herb && (B_MENTAL_HERB >= GEN_5 && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)) // mental herb
{ {
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // AI goes first if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // AI goes first
{ {
if (gLastMoves[battlerDef] != MOVE_NONE if (gLastMoves[battlerDef] != MOVE_NONE
&& gLastMoves[battlerDef] != 0xFFFF) && gLastMoves[battlerDef] != 0xFFFF)
@ -3594,7 +3617,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score += 3; score += 3;
break; break;
case EFFECT_DESTINY_BOND: case EFFECT_DESTINY_BOND:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER && CanTargetFaintAi(battlerDef, battlerAtk)) if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER && CanTargetFaintAi(battlerDef, battlerAtk))
score += 3; score += 3;
break; break;
case EFFECT_SPITE: case EFFECT_SPITE:
@ -3703,7 +3726,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
{ {
ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score); ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score);
} }
else if (isDoubleBattle && AI_GetBattlerMoveTargetType(AI_DATA->battlerAtkPartner, AI_DATA->partnerMove) & MOVE_TARGET_FOES_AND_ALLY) else if (isDoubleBattle && AI_GetBattlerMoveTargetType(BATTLE_PARTNER(battlerAtk), AI_DATA->partnerMove) & MOVE_TARGET_FOES_AND_ALLY)
{ {
if (AI_DATA->abilities[battlerAtk] != ABILITY_TELEPATHY) if (AI_DATA->abilities[battlerAtk] != ABILITY_TELEPATHY)
ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score); ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score);
@ -3842,7 +3865,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
&& AI_DATA->abilities[battlerAtk] != ABILITY_CONTRARY && AI_DATA->abilities[battlerAtk] != ABILITY_CONTRARY
&& CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) && CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
{ {
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker goes first
score += 9; score += 9;
else else
score += 3; score += 3;
@ -3887,7 +3910,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score++; score++;
if (predictedMove != MOVE_NONE && !isDoubleBattle) if (predictedMove != MOVE_NONE && !isDoubleBattle)
{ {
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker goes first
{ {
if (gBattleMoves[predictedMove].effect == EFFECT_EXPLOSION if (gBattleMoves[predictedMove].effect == EFFECT_EXPLOSION
|| gBattleMoves[predictedMove].effect == EFFECT_PROTECT) || gBattleMoves[predictedMove].effect == EFFECT_PROTECT)
@ -3954,7 +3977,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break; break;
case EFFECT_ATTRACT: case EFFECT_ATTRACT:
if (!isDoubleBattle && BattlerWillFaintFromSecondaryDamage(battlerDef, AI_DATA->abilities[battlerDef]) if (!isDoubleBattle && BattlerWillFaintFromSecondaryDamage(battlerDef, AI_DATA->abilities[battlerDef])
&& AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Target goes first && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Target goes first
break; // Don't use if the attract won't have a change to activate break; // Don't use if the attract won't have a change to activate
if (gBattleMons[battlerDef].status1 & STATUS1_ANY if (gBattleMons[battlerDef].status1 & STATUS1_ANY
@ -3999,7 +4022,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (isDoubleBattle) if (isDoubleBattle)
{ {
if (IsHazardMoveEffect(gBattleMoves[AI_DATA->partnerMove].effect) // Partner is going to set up hazards if (IsHazardMoveEffect(gBattleMoves[AI_DATA->partnerMove].effect) // Partner is going to set up hazards
&& AI_WhoStrikesFirst(battlerAtk, BATTLE_PARTNER(battlerAtk)) == AI_IS_SLOWER) // Partner going first && AI_WhoStrikesFirst(battlerAtk, BATTLE_PARTNER(battlerAtk), move) == AI_IS_SLOWER) // Partner going first
break; // Don't use Defog if partner is going to set up hazards break; // Don't use Defog if partner is going to set up hazards
} }
@ -4488,13 +4511,13 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score++; score++;
break; break;
case EFFECT_THROAT_CHOP: case EFFECT_THROAT_CHOP:
if (predictedMove != MOVE_NONE && TestMoveFlags(predictedMove, FLAG_SOUND) && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) if (predictedMove != MOVE_NONE && TestMoveFlags(predictedMove, FLAG_SOUND) && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
score += 3; // Ai goes first and predicts the target will use a sound move score += 3; // Ai goes first and predicts the target will use a sound move
else if (TestMoveFlagsInMoveset(battlerDef, FLAG_SOUND)) else if (TestMoveFlagsInMoveset(battlerDef, FLAG_SOUND))
score += 3; score += 3;
break; break;
case EFFECT_HEAL_BLOCK: case EFFECT_HEAL_BLOCK:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER && predictedMove != MOVE_NONE && IsHealingMoveEffect(gBattleMoves[predictedMove].effect)) if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER && predictedMove != MOVE_NONE && IsHealingMoveEffect(gBattleMoves[predictedMove].effect))
score += 3; // Try to cancel healing move score += 3; // Try to cancel healing move
else if (HasHealingEffect(battlerDef) || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_LEFTOVERS else if (HasHealingEffect(battlerDef) || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_LEFTOVERS
|| (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_BLACK_SLUDGE && IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON))) || (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_BLACK_SLUDGE && IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON)))
@ -4530,7 +4553,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break; break;
case EFFECT_QUASH: case EFFECT_QUASH:
if (isDoubleBattle if (isDoubleBattle
&& AI_WhoStrikesFirst(BATTLE_PARTNER(battlerAtk), battlerDef) == AI_IS_SLOWER) // Attacker partner wouldn't go before target && AI_WhoStrikesFirst(BATTLE_PARTNER(battlerAtk), battlerDef, AI_DATA->partnerMove) == AI_IS_SLOWER) // Attacker partner wouldn't go before target
score++; score++;
break; break;
case EFFECT_TAILWIND: case EFFECT_TAILWIND:
@ -4552,7 +4575,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (IsBattlerGrounded(battlerAtk) && HasDamagingMoveOfType(battlerDef, TYPE_ELECTRIC) if (IsBattlerGrounded(battlerAtk) && HasDamagingMoveOfType(battlerDef, TYPE_ELECTRIC)
&& !(AI_GetTypeEffectiveness(MOVE_EARTHQUAKE, battlerDef, battlerAtk) == AI_EFFECTIVENESS_x0)) // Doesn't resist ground move && !(AI_GetTypeEffectiveness(MOVE_EARTHQUAKE, battlerDef, battlerAtk) == AI_EFFECTIVENESS_x0)) // Doesn't resist ground move
{ {
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker goes first
{ {
if (gBattleMoves[predictedMove].type == TYPE_GROUND) if (gBattleMoves[predictedMove].type == TYPE_GROUND)
score += 3; // Cause the enemy's move to fail score += 3; // Cause the enemy's move to fail
@ -4567,7 +4590,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
} }
break; break;
case EFFECT_CAMOUFLAGE: case EFFECT_CAMOUFLAGE:
if (predictedMove != MOVE_NONE && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER // Attacker goes first if (predictedMove != MOVE_NONE && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER // Attacker goes first
&& !IS_MOVE_STATUS(move) && AI_GetTypeEffectiveness(predictedMove, battlerDef, battlerAtk) != AI_EFFECTIVENESS_x0) && !IS_MOVE_STATUS(move) && AI_GetTypeEffectiveness(predictedMove, battlerDef, battlerAtk) != AI_EFFECTIVENESS_x0)
score++; score++;
break; break;
@ -4610,7 +4633,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
} }
break; break;
case EFFECT_FLAIL: case EFFECT_FLAIL:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Ai goes first if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Ai goes first
{ {
if (AI_DATA->hpPercents[battlerAtk] < 20) if (AI_DATA->hpPercents[battlerAtk] < 20)
score++; score++;
@ -4652,7 +4675,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score += 2; score += 2;
break; break;
case EFFECT_ENDEAVOR: case EFFECT_ENDEAVOR:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent faster if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Opponent faster
{ {
if (AI_DATA->hpPercents[battlerAtk] < 40) if (AI_DATA->hpPercents[battlerAtk] < 40)
score++; score++;
@ -4683,7 +4706,7 @@ static s16 AI_SetupFirstTurn(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
return score; return score;
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING
&& AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER
&& CanTargetFaintAi(battlerDef, battlerAtk) && CanTargetFaintAi(battlerDef, battlerAtk)
&& GetMovePriority(battlerAtk, move) == 0) && GetMovePriority(battlerAtk, move) == 0)
{ {

View File

@ -18,6 +18,8 @@
static bool8 HasSuperEffectiveMoveAgainstOpponents(bool8 noRng); static bool8 HasSuperEffectiveMoveAgainstOpponents(bool8 noRng);
static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent); static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent);
static bool8 ShouldUseItem(void); static bool8 ShouldUseItem(void);
static bool32 AI_ShouldHeal(u32 healAmount);
static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount);
void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId) void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId)
{ {
@ -788,6 +790,25 @@ static u8 GetAI_ItemType(u16 itemId, const u8 *itemEffect)
return AI_ITEM_NOT_RECOGNIZABLE; return AI_ITEM_NOT_RECOGNIZABLE;
} }
static bool32 AiExpectsToFaintPlayer(void)
{
bool32 canFaintPlayer;
u32 i;
u8 target = gBattleStruct->aiChosenTarget[gActiveBattler];
if (gBattleStruct->aiMoveOrAction[gActiveBattler] > 3)
return FALSE; // AI not planning to use move
if (GetBattlerSide(target) != GetBattlerSide(gActiveBattler)
&& CanIndexMoveFaintTarget(gActiveBattler, target, gBattleStruct->aiMoveOrAction[gActiveBattler], 0)
&& AI_WhoStrikesFirst(gActiveBattler, target, GetAIChosenMove(gActiveBattler)) == AI_IS_FASTER) {
// We expect to faint the target and move first -> dont use an item
return TRUE;
}
return FALSE;
}
static bool8 ShouldUseItem(void) static bool8 ShouldUseItem(void)
{ {
struct Pokemon *party; struct Pokemon *party;
@ -802,6 +823,9 @@ static bool8 ShouldUseItem(void)
if (gStatuses3[gActiveBattler] & STATUS3_EMBARGO) if (gStatuses3[gActiveBattler] & STATUS3_EMBARGO)
return FALSE; return FALSE;
if (AiExpectsToFaintPlayer())
return FALSE;
if (GetBattlerSide(gActiveBattler) == B_SIDE_PLAYER) if (GetBattlerSide(gActiveBattler) == B_SIDE_PLAYER)
party = gPlayerParty; party = gPlayerParty;
@ -843,21 +867,10 @@ static bool8 ShouldUseItem(void)
switch (*(gBattleStruct->AI_itemType + gActiveBattler / 2)) switch (*(gBattleStruct->AI_itemType + gActiveBattler / 2))
{ {
case AI_ITEM_FULL_RESTORE: case AI_ITEM_FULL_RESTORE:
if (gBattleMons[gActiveBattler].hp >= gBattleMons[gActiveBattler].maxHP / 4) shouldUse = AI_ShouldHeal(0);
break;
if (gBattleMons[gActiveBattler].hp == 0)
break;
shouldUse = TRUE;
break; break;
case AI_ITEM_HEAL_HP: case AI_ITEM_HEAL_HP:
paramOffset = GetItemEffectParamOffset(item, 4, 4); shouldUse = AI_ShouldHeal(itemEffects[GetItemEffectParamOffset(item, 4, 4)]);
if (paramOffset == 0)
break;
if (gBattleMons[gActiveBattler].hp == 0)
break;
if (gBattleMons[gActiveBattler].hp < gBattleMons[gActiveBattler].maxHP / 4
|| gBattleMons[gActiveBattler].maxHP - gBattleMons[gActiveBattler].hp > itemEffects[paramOffset])
shouldUse = TRUE;
break; break;
case AI_ITEM_CURE_CONDITION: case AI_ITEM_CURE_CONDITION:
*(gBattleStruct->AI_itemFlags + gActiveBattler / 2) = 0; *(gBattleStruct->AI_itemFlags + gActiveBattler / 2) = 0;
@ -948,3 +961,32 @@ static bool8 ShouldUseItem(void)
return FALSE; return FALSE;
} }
static bool32 AI_ShouldHeal(u32 healAmount)
{
bool32 shouldHeal = FALSE;
if (gBattleMons[gActiveBattler].hp < gBattleMons[gActiveBattler].maxHP / 4
|| gBattleMons[gActiveBattler].hp == 0
|| (healAmount != 0 && gBattleMons[gActiveBattler].maxHP - gBattleMons[gActiveBattler].hp > healAmount)) {
// We have low enough HP to consider healing
shouldHeal = !AI_OpponentCanFaintAiWithMod(healAmount); // if target can kill us even after we heal, why bother
}
return shouldHeal;
}
static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount)
{
u32 i;
// Check special cases to NOT heal
for (i = 0; i < gBattlersCount; i++) {
if (GetBattlerSide(i) == B_SIDE_PLAYER) {
if (CanTargetFaintAiWithMod(i, gActiveBattler, healAmount, 0)) {
// Target is expected to faint us
return TRUE;
}
}
}
return FALSE;
}

View File

@ -440,9 +440,14 @@ static const u16 sOtherMoveCallingMoves[] =
}; };
// Functions // Functions
u16 GetAIChosenMove(u8 battlerId)
{
return (gBattleMons[battlerId].moves[gBattleStruct->aiMoveOrAction[battlerId]]);
}
bool32 WillAIStrikeFirst(void) bool32 WillAIStrikeFirst(void)
{ {
return (AI_WhoStrikesFirst(sBattler_AI, gBattlerTarget) == AI_IS_FASTER); return (AI_WhoStrikesFirst(sBattler_AI, gBattlerTarget, AI_THINKING_STRUCT->moveConsidered) == AI_IS_FASTER);
} }
bool32 AI_RandLessThan(u8 val) bool32 AI_RandLessThan(u8 val)
@ -638,7 +643,7 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler)
u32 move = gBattleResources->battleHistory->usedMoves[opposingBattler][i]; u32 move = gBattleResources->battleHistory->usedMoves[opposingBattler][i];
if (gBattleMoves[move].effect == EFFECT_PROTECT && move != MOVE_ENDURE) if (gBattleMoves[move].effect == EFFECT_PROTECT && move != MOVE_ENDURE)
return TRUE; return TRUE;
if (gBattleMoves[move].effect == EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAI, opposingBattler) == AI_IS_SLOWER) if (gBattleMoves[move].effect == EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAI, opposingBattler, GetAIChosenMove(battlerAI)) == AI_IS_SLOWER)
return TRUE; return TRUE;
} }
return FALSE; return FALSE;
@ -987,23 +992,25 @@ static u8 AI_GetEffectiveness(u16 multiplier)
* AI_IS_FASTER: is user(ai) faster * AI_IS_FASTER: is user(ai) faster
* AI_IS_SLOWER: is target faster * AI_IS_SLOWER: is target faster
*/ */
u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2) u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2, u16 moveConsidered)
{ {
u32 fasterAI = 0, fasterPlayer = 0, i; u32 fasterAI = 0, fasterPlayer = 0, i;
s8 prioAI = 0; s8 prioAI = 0;
s8 prioPlayer = 0; s8 prioPlayer = 0;
s8 prioBattler2 = 0;
u16 *battler2Moves = GetMovesArray(battler2);
// Check move priorities first. // Check move priorities first.
prioAI = GetMovePriority(battlerAI, AI_THINKING_STRUCT->moveConsidered); prioAI = GetMovePriority(battlerAI, moveConsidered);
for (i = 0; i < MAX_MON_MOVES; i++) for (i = 0; i < MAX_MON_MOVES; i++)
{ {
if (gBattleMons[battler2].moves[i] == 0 || gBattleMons[battler2].moves[i] == 0xFFFF) if (battler2Moves[i] == 0 || battler2Moves[i] == 0xFFFF)
continue; continue;
prioPlayer = GetMovePriority(battler2, gBattleMons[battler2].moves[i]); prioBattler2 = GetMovePriority(battler2, battler2Moves[i]);
if (prioAI > prioPlayer) if (prioAI > prioBattler2)
fasterAI++; fasterAI++;
else if (prioPlayer > prioAI) else if (prioBattler2 > prioAI)
fasterPlayer++; fasterPlayer++;
} }
@ -1017,6 +1024,8 @@ u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2)
} }
else else
{ {
if (prioAI > prioBattler2)
return AI_IS_FASTER; // if we didn't know any of battler 2's moves to compare priorities, assume they don't have a prio+ move
// Priorities are the same(at least comparing to moves the AI is aware of), decide by speed. // Priorities are the same(at least comparing to moves the AI is aware of), decide by speed.
if (GetWhoStrikesFirst(battlerAI, battler2, TRUE) == 0) if (GetWhoStrikesFirst(battlerAI, battler2, TRUE) == 0)
return AI_IS_FASTER; return AI_IS_FASTER;
@ -2422,7 +2431,7 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo
/*if (IsPredictedToSwitch(battlerDef, battlerAtk) && !hasStatBoost) /*if (IsPredictedToSwitch(battlerDef, battlerAtk) && !hasStatBoost)
return PIVOT; // Try pivoting so you can switch to a better matchup to counter your new opponent*/ return PIVOT; // Try pivoting so you can switch to a better matchup to counter your new opponent*/
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker goes first
{ {
if (!CanAIFaintTarget(battlerAtk, battlerDef, 0)) // Can't KO foe otherwise if (!CanAIFaintTarget(battlerAtk, battlerDef, 0)) // Can't KO foe otherwise
{ {
@ -2782,7 +2791,7 @@ u32 ShouldTryToFlinch(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbili
{ {
if (defAbility == ABILITY_INNER_FOCUS if (defAbility == ABILITY_INNER_FOCUS
|| DoesSubstituteBlockMove(battlerAtk, battlerDef, move) || DoesSubstituteBlockMove(battlerAtk, battlerDef, move)
|| AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent goes first || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Opponent goes first
{ {
return 0; // don't try to flinch return 0; // don't try to flinch
} }
@ -2906,7 +2915,7 @@ bool32 ShouldUseRecoilMove(u8 battlerAtk, u8 battlerDef, u32 recoilDmg, u8 moveI
bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage) bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage)
{ {
if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
{ {
// using item or user goes first // using item or user goes first
u8 healPercent = (gBattleMoves[move].argument == 0) ? 50 : gBattleMoves[move].argument; u8 healPercent = (gBattleMoves[move].argument == 0) ? 50 : gBattleMoves[move].argument;
@ -2933,7 +2942,7 @@ bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage)
bool32 ShouldRecover(u8 battlerAtk, u8 battlerDef, u16 move, u8 healPercent) bool32 ShouldRecover(u8 battlerAtk, u8 battlerDef, u16 move, u8 healPercent)
{ {
if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
{ {
// using item or user going first // using item or user going first
s32 damage = AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]; s32 damage = AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex];

View File

@ -1535,7 +1535,7 @@ static void OpponentHandlePrintSelectionString(void)
static void OpponentHandleChooseAction(void) static void OpponentHandleChooseAction(void)
{ {
AI_TrySwitchOrUseItem(); // TODO consider move choice first AI_TrySwitchOrUseItem();
OpponentBufferExecCompleted(); OpponentBufferExecCompleted();
} }
@ -1559,9 +1559,8 @@ static void OpponentHandleChooseMove(void)
if (gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER) if (gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER)
|| IsWildMonSmart()) || IsWildMonSmart())
{ {
BattleAI_SetupAIData(0xF); chosenMoveId = gBattleStruct->aiMoveOrAction[gActiveBattler];
chosenMoveId = BattleAI_ChooseMoveOrAction(); gBattlerTarget = gBattleStruct->aiChosenTarget[gActiveBattler];
switch (chosenMoveId) switch (chosenMoveId)
{ {
case AI_CHOICE_WATCH: case AI_CHOICE_WATCH:

View File

@ -1515,8 +1515,8 @@ static void PlayerPartnerHandleChooseMove(void)
u8 chosenMoveId; u8 chosenMoveId;
struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct*)(&gBattleResources->bufferA[gActiveBattler][4]); struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct*)(&gBattleResources->bufferA[gActiveBattler][4]);
BattleAI_SetupAIData(0xF); chosenMoveId = gBattleStruct->aiMoveOrAction[gActiveBattler];
chosenMoveId = BattleAI_ChooseMoveOrAction(); gBattlerTarget = gBattleStruct->aiChosenTarget[gActiveBattler];
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED)) if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED))
gBattlerTarget = gActiveBattler; gBattlerTarget = gActiveBattler;

View File

@ -3918,6 +3918,11 @@ static void HandleTurnActionSelectionState(void)
case STATE_TURN_START_RECORD: // Recorded battle related action on start of every turn. case STATE_TURN_START_RECORD: // Recorded battle related action on start of every turn.
RecordedBattle_CopyBattlerMoves(); RecordedBattle_CopyBattlerMoves();
gBattleCommunication[gActiveBattler] = STATE_BEFORE_ACTION_CHOSEN; gBattleCommunication[gActiveBattler] = STATE_BEFORE_ACTION_CHOSEN;
// Do AI score computations here so we can use them in AI_TrySwitchOrUseItem
if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart()) && IsBattlerAIControlled(gActiveBattler)) {
gBattleStruct->aiMoveOrAction[gActiveBattler] = ComputeBattleAiScores(gActiveBattler);
}
break; break;
case STATE_BEFORE_ACTION_CHOSEN: // Choose an action. case STATE_BEFORE_ACTION_CHOSEN: // Choose an action.
*(gBattleStruct->monToSwitchIntoId + gActiveBattler) = PARTY_SIZE; *(gBattleStruct->monToSwitchIntoId + gActiveBattler) = PARTY_SIZE;