diff --git a/include/battle.h b/include/battle.h index 5d0b0596d..bcf018d0f 100644 --- a/include/battle.h +++ b/include/battle.h @@ -584,6 +584,8 @@ struct BattleStruct bool8 spriteIgnore0Hp; struct Illusion illusion[MAX_BATTLERS_COUNT]; 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 friskedBattler; // Frisk needs to identify 2 battlers in double battles. bool8 friskedAbility; // If identifies two mons, show the ability pop-up only once. diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index 185281749..fcb31a9b2 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -19,7 +19,7 @@ return score; \ } - +u8 ComputeBattleAiScores(u8 battler); void BattleAI_SetupItems(void); void BattleAI_SetupFlags(void); void BattleAI_SetupAIData(u8 defaultScoreMoves); diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 7cc42e74a..437c475f7 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -20,6 +20,7 @@ void ClearBattlerItemEffectHistory(u8 battlerId); void SaveBattlerData(u8 battlerId); void SetBattlerData(u8 battlerId); void RestoreBattlerData(u8 battlerId); +u16 GetAIChosenMove(u8 battlerId); bool32 WillAIStrikeFirst(void); u32 GetTotalBaseStat(u32 species); @@ -27,7 +28,7 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler); bool32 AtMaxHp(u8 battler); u32 GetHealthPercentage(u8 battler); 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 CanMoveFaintBattler(u16 move, u8 battlerDef, u8 battlerAtk, u8 nHits); bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgMod); diff --git a/include/constants/battle.h b/include/constants/battle.h index d651dab18..71252ae34 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -81,6 +81,8 @@ #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_TYPE_HAS_AI (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER) + // Battle Outcome defines #define B_OUTCOME_WON 1 diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 6601862ed..dc3bd6b26 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -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. } +// sBattler_AI set in ComputeBattleAiScores void BattleAI_SetupAIData(u8 defaultScoreMoves) { s32 i, move, dmg; u8 moveLimitations; - + // Clear AI data but preserve the flags. u32 flags = AI_THINKING_STRUCT->aiFlags; memset(AI_THINKING_STRUCT, 0, sizeof(struct AI_ThinkingStruct)); @@ -204,8 +205,9 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves) AI_THINKING_STRUCT->score[i] = 0; } - sBattler_AI = gActiveBattler; + //sBattler_AI = gActiveBattler; gBattlerTarget = SetRandomTarget(sBattler_AI); + gBattleStruct->aiChosenTarget[sBattler_AI] = gBattlerTarget; } u8 BattleAI_ChooseMoveOrAction(void) @@ -226,53 +228,73 @@ u8 BattleAI_ChooseMoveOrAction(void) 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) { u32 battlerAtk, battlerDef, i, move; u8 effectiveness; s32 dmg; + memset(AI_DATA, 0, sizeof(struct AiLogicData)); + if (!(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER) && !IsWildMonSmart())) return; - memset(AI_DATA, 0, sizeof(struct AiLogicData)); - for (battlerAtk = 0; battlerAtk < MAX_BATTLERS_COUNT; battlerAtk++) + // get/assume all battler data + for (i = 0; i < gBattlersCount; i++) { - if (IsBattlerAlive(battlerAtk)) { - AI_DATA->abilities[battlerAtk] = AI_GetAbility(battlerAtk); - 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]; - AI_DATA->hpPercents[battlerAtk] = GetHealthPercentage(battlerAtk); - AI_DATA->moveLimitations[battlerAtk] = CheckMoveLimitations(battlerAtk, 0, MOVE_LIMITATIONS_ALL); + if (IsBattlerAlive(i)) { + SetBattlerAiData(i); + } + } + + // simulate AI damage + for (battlerAtk = 0; battlerAtk < gBattlersCount; battlerAtk++) + { + 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 - for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++) + RecordKnownMove(battlerDef, gLastMoves[battlerDef]); + for (i = 0; i < MAX_MON_MOVES; i++) { - if (battlerAtk == battlerDef) - continue; + dmg = 0; + effectiveness = AI_EFFECTIVENESS_x0; + move = gBattleMons[battlerAtk].moves[i]; - RecordKnownMove(battlerDef, gLastMoves[battlerDef]); - for (i = 0; i < MAX_MON_MOVES; i++) - { - dmg = 0; - effectiveness = AI_EFFECTIVENESS_x0; - 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; + if (move != 0 + && move != 0xFFFF + //&& gBattleMoves[move].power != 0 /* we want to get effectiveness of status moves */ + && !(AI_DATA->moveLimitations[battlerAtk] & gBitTable[i])) { + dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &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]; + gBattleStruct->aiChosenTarget[sBattler_AI] = 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; s32 moveType; 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]; bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk); u32 i; @@ -593,7 +616,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) } // 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 // 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) && !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) 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) && !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) score -= 10; // no anticipated move to encore @@ -1755,7 +1778,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_SPITE: 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 || gLastMoves[battlerDef] == 0xFFFF) @@ -1912,7 +1935,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) if (isDoubleBattle) { 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; 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; case EFFECT_SEMI_INVULNERABLE: 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) 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: 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 else return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score); @@ -2320,7 +2343,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) } break; 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 || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, AI_DATA->partnerMove)) score -= 10; @@ -2349,7 +2372,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) case EFFECT_INSTRUCT: { u16 instructedMove; - if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) instructedMove = predictedMove; else instructedMove = gLastMoves[battlerDef]; @@ -2389,21 +2412,21 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_QUASH: 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)) score -= 10; break; case EFFECT_AFTER_YOU: if (!IsTargetingPartner(battlerAtk, battlerDef) || !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)) score -= 10; break; case EFFECT_SUCKER_PUNCH: 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; } break; @@ -2455,7 +2478,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) score--; // don't want to move last break; 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) score -= 4; 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 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 if (IsAttackBoostMoveEffect(effect)) @@ -2871,7 +2894,7 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) case EFFECT_INSTRUCT: { u16 instructedMove; - if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner) == AI_IS_FASTER) + if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner, move) == AI_IS_FASTER) instructedMove = AI_DATA->partnerMove; else instructedMove = gLastMoves[battlerAtkPartner]; @@ -2885,8 +2908,8 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) } break; case EFFECT_AFTER_YOU: - if (AI_WhoStrikesFirst(battlerAtkPartner, FOE(battlerAtkPartner) == 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 + 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_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) 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: if (!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))) score += 2; 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 if (!IsBattlerIncapacitated(battlerDef, AI_DATA->abilities[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) score++; @@ -3034,7 +3057,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) case ABILITY_GRIM_NEIGH: case ABILITY_AS_ONE_ICE_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)) 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++; break; 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) 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 && (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 && gLastMoves[battlerDef] != 0xFFFF) @@ -3594,7 +3617,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) score += 3; break; 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; break; 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); } - 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) 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 && 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; else score += 3; @@ -3887,7 +3910,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) score++; 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 || gBattleMoves[predictedMove].effect == EFFECT_PROTECT) @@ -3954,7 +3977,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_ATTRACT: 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 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 (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 } @@ -4488,13 +4511,13 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) score++; break; 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 else if (TestMoveFlagsInMoveset(battlerDef, FLAG_SOUND)) score += 3; break; 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 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))) @@ -4530,7 +4553,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_QUASH: 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++; break; 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) && !(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) 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; 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) score++; break; @@ -4610,7 +4633,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) } break; 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) score++; @@ -4652,7 +4675,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) score += 2; break; 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) score++; @@ -4683,7 +4706,7 @@ static s16 AI_SetupFirstTurn(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) return score; 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) && GetMovePriority(battlerAtk, move) == 0) { diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 9136d667c..7963d8b86 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -18,6 +18,8 @@ static bool8 HasSuperEffectiveMoveAgainstOpponents(bool8 noRng); static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent); static bool8 ShouldUseItem(void); +static bool32 AI_ShouldHeal(u32 healAmount); +static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount); 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; } +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) { struct Pokemon *party; @@ -802,6 +823,9 @@ static bool8 ShouldUseItem(void) if (gStatuses3[gActiveBattler] & STATUS3_EMBARGO) return FALSE; + + if (AiExpectsToFaintPlayer()) + return FALSE; if (GetBattlerSide(gActiveBattler) == B_SIDE_PLAYER) party = gPlayerParty; @@ -843,21 +867,10 @@ static bool8 ShouldUseItem(void) switch (*(gBattleStruct->AI_itemType + gActiveBattler / 2)) { case AI_ITEM_FULL_RESTORE: - if (gBattleMons[gActiveBattler].hp >= gBattleMons[gActiveBattler].maxHP / 4) - break; - if (gBattleMons[gActiveBattler].hp == 0) - break; - shouldUse = TRUE; + shouldUse = AI_ShouldHeal(0); break; case AI_ITEM_HEAL_HP: - paramOffset = 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; + shouldUse = AI_ShouldHeal(itemEffects[GetItemEffectParamOffset(item, 4, 4)]); break; case AI_ITEM_CURE_CONDITION: *(gBattleStruct->AI_itemFlags + gActiveBattler / 2) = 0; @@ -948,3 +961,32 @@ static bool8 ShouldUseItem(void) 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; +} diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 3a43fd702..a5cbc92b0 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -440,9 +440,14 @@ static const u16 sOtherMoveCallingMoves[] = }; // Functions +u16 GetAIChosenMove(u8 battlerId) +{ + return (gBattleMons[battlerId].moves[gBattleStruct->aiMoveOrAction[battlerId]]); +} + 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) @@ -638,7 +643,7 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler) u32 move = gBattleResources->battleHistory->usedMoves[opposingBattler][i]; if (gBattleMoves[move].effect == EFFECT_PROTECT && move != MOVE_ENDURE) 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 FALSE; @@ -987,23 +992,25 @@ static u8 AI_GetEffectiveness(u16 multiplier) * AI_IS_FASTER: is user(ai) 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; s8 prioAI = 0; s8 prioPlayer = 0; + s8 prioBattler2 = 0; + u16 *battler2Moves = GetMovesArray(battler2); // Check move priorities first. - prioAI = GetMovePriority(battlerAI, AI_THINKING_STRUCT->moveConsidered); + prioAI = GetMovePriority(battlerAI, moveConsidered); 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; - prioPlayer = GetMovePriority(battler2, gBattleMons[battler2].moves[i]); - if (prioAI > prioPlayer) + prioBattler2 = GetMovePriority(battler2, battler2Moves[i]); + if (prioAI > prioBattler2) fasterAI++; - else if (prioPlayer > prioAI) + else if (prioBattler2 > prioAI) fasterPlayer++; } @@ -1017,6 +1024,8 @@ u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2) } 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. if (GetWhoStrikesFirst(battlerAI, battler2, TRUE) == 0) 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) 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 { @@ -2782,7 +2791,7 @@ u32 ShouldTryToFlinch(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbili { if (defAbility == ABILITY_INNER_FOCUS || 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 } @@ -2906,7 +2915,7 @@ bool32 ShouldUseRecoilMove(u8 battlerAtk, u8 battlerDef, u32 recoilDmg, u8 moveI 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 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) { - 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 s32 damage = AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]; diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c index f3b8352bb..9c262405b 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -1535,7 +1535,7 @@ static void OpponentHandlePrintSelectionString(void) static void OpponentHandleChooseAction(void) { - AI_TrySwitchOrUseItem(); // TODO consider move choice first + AI_TrySwitchOrUseItem(); OpponentBufferExecCompleted(); } @@ -1559,9 +1559,8 @@ static void OpponentHandleChooseMove(void) if (gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER) || IsWildMonSmart()) { - BattleAI_SetupAIData(0xF); - chosenMoveId = BattleAI_ChooseMoveOrAction(); - + chosenMoveId = gBattleStruct->aiMoveOrAction[gActiveBattler]; + gBattlerTarget = gBattleStruct->aiChosenTarget[gActiveBattler]; switch (chosenMoveId) { case AI_CHOICE_WATCH: diff --git a/src/battle_controller_player_partner.c b/src/battle_controller_player_partner.c index 984a647f8..4e41651de 100644 --- a/src/battle_controller_player_partner.c +++ b/src/battle_controller_player_partner.c @@ -1515,8 +1515,8 @@ static void PlayerPartnerHandleChooseMove(void) u8 chosenMoveId; struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct*)(&gBattleResources->bufferA[gActiveBattler][4]); - BattleAI_SetupAIData(0xF); - chosenMoveId = BattleAI_ChooseMoveOrAction(); + chosenMoveId = gBattleStruct->aiMoveOrAction[gActiveBattler]; + gBattlerTarget = gBattleStruct->aiChosenTarget[gActiveBattler]; if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED)) gBattlerTarget = gActiveBattler; diff --git a/src/battle_main.c b/src/battle_main.c index 5bf5043e1..cb38f3bdf 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3918,6 +3918,11 @@ static void HandleTurnActionSelectionState(void) case STATE_TURN_START_RECORD: // Recorded battle related action on start of every turn. RecordedBattle_CopyBattlerMoves(); 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; case STATE_BEFORE_ACTION_CHOSEN: // Choose an action. *(gBattleStruct->monToSwitchIntoId + gActiveBattler) = PARTY_SIZE;