diff --git a/include/battle.h b/include/battle.h index 60c583e503..04bf4bf19b 100644 --- a/include/battle.h +++ b/include/battle.h @@ -291,6 +291,7 @@ struct AiLogicData u8 moveDmgResult[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // MOVE_POWER defines for GetMoveDamageResult ; attacker, target, moveIndex s32 simulatedDmg[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex u8 effectiveness[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex + u8 moveAccuracy[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex u8 moveLimitations[MAX_BATTLERS_COUNT]; bool8 shouldSwitchMon; // Because all available moves have no/little effect. Each bit per battler. u8 monToSwitchId[MAX_BATTLERS_COUNT]; // ID of the mon to switch. @@ -709,11 +710,13 @@ STATIC_ASSERT(sizeof(((struct BattleStruct *)0)->palaceFlags) * 8 >= MAX_BATTLER #define IS_MOVE_SPECIAL(move)(GetBattleMoveSplit(move) == SPLIT_SPECIAL) #define IS_MOVE_STATUS(move)(gBattleMoves[move].split == SPLIT_STATUS) -#define IS_MOVE_RECOIL(move)(gBattleMoves[move].effect == EFFECT_RECOIL_25 \ - || gBattleMoves[move].effect == EFFECT_RECOIL_IF_MISS \ - || gBattleMoves[move].effect == EFFECT_RECOIL_50 \ - || gBattleMoves[move].effect == EFFECT_RECOIL_33 \ - || gBattleMoves[move].effect == EFFECT_RECOIL_33_STATUS) +#define IS_EFFECT_RECOIL(effect)(effect == EFFECT_RECOIL_25 \ + || effect == EFFECT_RECOIL_IF_MISS \ + || effect == EFFECT_RECOIL_50 \ + || effect == EFFECT_RECOIL_33 \ + || effect == EFFECT_RECOIL_33_STATUS) + +#define IS_MOVE_RECOIL(move)(IS_EFFECT_RECOIL(gBattleMoves[move].effect)) #define BATTLER_MAX_HP(battlerId)(gBattleMons[battlerId].hp == gBattleMons[battlerId].maxHP) #define TARGET_TURN_DAMAGED ((gSpecialStatuses[gBattlerTarget].physicalDmg != 0 || gSpecialStatuses[gBattlerTarget].specialDmg != 0)) diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index b91865a08a..8de2462d90 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -34,8 +34,8 @@ u32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 moveConsidered); bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk); bool32 CanTargetMoveFaintAi(u32 move, u32 battlerDef, u32 battlerAtk, u32 nHits); bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dmgMod); -s32 AI_GetAbility(u32 battlerId); -u32 AI_GetHoldEffect(u32 battlerId); +s32 AI_DecideKnownAbilityForTurn(u32 battlerId); +u32 AI_DecideHoldEffectForTurn(u32 battlerId); u32 AI_GetMoveAccuracy(u32 battlerAtk, u32 battlerDef, u32 move); bool32 DoesBattlerIgnoreAbilityChecks(u32 atkAbility, u32 move); u32 AI_GetWeather(struct AiLogicData *aiData); @@ -85,10 +85,15 @@ bool32 ShouldLowerEvasion(u32 battlerAtk, u32 battlerDef, u32 defAbility); // move checks bool32 IsAffectedByPowder(u32 battler, u32 ability, u32 holdEffect); bool32 MovesWithSplitUnusable(u32 attacker, u32 target, u32 split); +u32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef); s32 AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower); s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather); +bool32 AI_IsDamagedByRecoil(u32 battler); u32 GetNoOfHitsToKO(u32 dmg, s32 hp); -void SetMoveDamageResult(u32 battlerAtk, u16 *moves); +u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef); +u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex); +bool32 IsInIgnoredPowerfulMoveEffects(u32 effect); +void SetMovesDamageResults(u32 battlerAtk, u16 *moves); u32 GetMoveDamageResult(u32 battlerAtk, u32 battlerDef, u32 moveIndex); u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef); uq4_12_t AI_GetTypeEffectiveness(u32 move, u32 battlerAtk, u32 battlerDef); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 4f778530d5..b397b05e69 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -328,9 +328,9 @@ static void SetBattlerAiData(u32 battler, struct AiLogicData *aiData) { u32 ability, holdEffect; - ability = aiData->abilities[battler] = AI_GetAbility(battler); + ability = aiData->abilities[battler] = AI_DecideKnownAbilityForTurn(battler); aiData->items[battler] = gBattleMons[battler].item; - holdEffect = aiData->holdEffects[battler] = AI_GetHoldEffect(battler); + holdEffect = aiData->holdEffects[battler] = AI_DecideHoldEffectForTurn(battler); aiData->holdEffectParams[battler] = GetBattlerHoldEffectParam(battler); aiData->predictedMoves[battler] = gLastMoves[battler]; aiData->hpPercents[battler] = GetHealthPercentage(battler); @@ -338,6 +338,19 @@ static void SetBattlerAiData(u32 battler, struct AiLogicData *aiData) aiData->speedStats[battler] = GetBattlerTotalSpeedStatArgs(battler, ability, holdEffect); } +static u32 Ai_SetMoveAccuracy(struct AiLogicData *aiData, u32 battlerAtk, u32 battlerDef, u32 move) +{ + u32 accuracy; + u32 abilityAtk = aiData->abilities[battlerAtk]; + u32 abilityDef = aiData->abilities[battlerAtk]; + if (abilityAtk == ABILITY_NO_GUARD || abilityDef == ABILITY_NO_GUARD || gBattleMoves[move].accuracy == 0) // Moves with accuracy 0 or no guard ability always hit. + accuracy = 100; + else + accuracy = GetTotalAccuracy(battlerAtk, battlerDef, move, abilityAtk, abilityDef, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef]); + + return accuracy; +} + static void SetBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u32 battlersCount) { u32 battlerDef, i, weather; @@ -361,16 +374,18 @@ static void SetBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u3 if (move != 0 && move != 0xFFFF - //&& gBattleMoves[move].power != 0 /* we want to get effectiveness of status moves */ - && !(aiData->moveLimitations[battlerAtk] & gBitTable[i])) { + //&& gBattleMoves[move].power != 0 /* we want to get effectiveness and accuracy of status moves */ + && !(aiData->moveLimitations[battlerAtk] & gBitTable[i])) + { dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE, weather); + aiData->moveAccuracy[battlerAtk][battlerDef][i] = Ai_SetMoveAccuracy(aiData, battlerAtk, battlerDef, move); } aiData->simulatedDmg[battlerAtk][battlerDef][i] = dmg; aiData->effectiveness[battlerAtk][battlerDef][i] = effectiveness; } } - SetMoveDamageResult(battlerAtk, moves); + SetMovesDamageResults(battlerAtk, moves); } void SetAiLogicDataForTurn(struct AiLogicData *aiData) @@ -1843,7 +1858,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) score -= 6; break; case EFFECT_RECOIL_25: - if (aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD && aiData->abilities[battlerAtk] != ABILITY_ROCK_HEAD) + if (AI_IsDamagedByRecoil(battlerAtk)) { u32 recoilDmg = max(1, aiData->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] / 4); if (!ShouldUseRecoilMove(battlerAtk, battlerDef, recoilDmg, AI_THINKING_STRUCT->movesetIndex)) @@ -1853,7 +1868,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_RECOIL_33: case EFFECT_RECOIL_33_STATUS: - if (aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD && aiData->abilities[battlerAtk] != ABILITY_ROCK_HEAD) + if (AI_IsDamagedByRecoil(battlerAtk)) { u32 recoilDmg = max(1, aiData->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] / 3); if (!ShouldUseRecoilMove(battlerAtk, battlerDef, recoilDmg, AI_THINKING_STRUCT->movesetIndex)) @@ -1862,7 +1877,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; case EFFECT_RECOIL_50: - if (aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD && aiData->abilities[battlerAtk] != ABILITY_ROCK_HEAD) + if (AI_IsDamagedByRecoil(battlerAtk)) { u32 recoilDmg = max(1, aiData->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] / 2); if (!ShouldUseRecoilMove(battlerAtk, battlerDef, recoilDmg, AI_THINKING_STRUCT->movesetIndex)) @@ -2123,7 +2138,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_TAUNT: if (gDisableStructs[battlerDef].tauntTimer > 0 || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) - score--; + score -= 10; break; case EFFECT_BESTOW: if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_NONE @@ -2682,9 +2697,9 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { // this move can faint the target if (aiFaster || GetMovePriority(battlerAtk, move) > 0) - score += 4; // we go first or we're using priority move + score += 5; // we go first or we're using priority move else - score += 2; + score += 4; } else { @@ -3122,6 +3137,18 @@ static bool32 IsPinchBerryItemEffect(u32 holdEffect) return FALSE; } +static u32 CompareMoveAccuracies(u32 battlerAtk, u32 battlerDef, u32 moveSlot1, u32 moveSlot2) +{ + u32 acc1 = AI_DATA->moveAccuracy[battlerAtk][battlerDef][moveSlot1]; + u32 acc2 = AI_DATA->moveAccuracy[battlerAtk][battlerDef][moveSlot2]; + + if (acc1 > acc2) + return 0; + else if (acc2 > acc1) + return 1; + return 2; +} + static u32 GetAIMostDamagingMoveId(u32 battlerAtk, u32 battlerDef) { u32 i, id = 0; @@ -3135,13 +3162,100 @@ static u32 GetAIMostDamagingMoveId(u32 battlerAtk, u32 battlerDef) return id; } +static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId) +{ + u32 i; + u32 noOfHits[MAX_MON_MOVES]; + s32 score = 0; + u16 *moves = GetMovesArray(battlerAtk); + bool8 isPowerfulIgnoredEffect[MAX_MON_MOVES]; + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && gBattleMoves[moves[i]].power) + { + noOfHits[i] = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, i); + isPowerfulIgnoredEffect[i] = IsInIgnoredPowerfulMoveEffects(gBattleMoves[moves[i]].effect); + } + else + { + noOfHits[i] = 0; + isPowerfulIgnoredEffect[i] = FALSE; + } + } + + // if multiple moves can 0HKO, then compare the current move with the others + if (noOfHits[currId] == 1) + { + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (noOfHits[i] != 1 || i == currId) + continue; + + // prioritize moves without a risky secondary effect such as Overheat, Explosion or Hyper Beam + if (!isPowerfulIgnoredEffect[currId] && isPowerfulIgnoredEffect[i]) + score += 2; + else if (isPowerfulIgnoredEffect[currId] && !isPowerfulIgnoredEffect[i]) + score -= 2; + + if (!isPowerfulIgnoredEffect[currId]) + { + // prioritize moves with higher accuracy + switch (CompareMoveAccuracies(battlerAtk, battlerDef, currId, i)) + { + case 0: + score++; + break; + case 1: + score--; + break; + } + + // prioritize moves without recoil + if (AI_IsDamagedByRecoil(battlerAtk)) + { + if (!IS_MOVE_RECOIL(moves[currId]) && IS_MOVE_RECOIL(moves[i])) + score++; + else if (IS_MOVE_RECOIL(moves[currId]) && !IS_MOVE_RECOIL(moves[i])) + score--; + } + } + } + } + else + { + // give priority to moves which can ko the target faster + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (i != currId && noOfHits[i] < 5 && noOfHits[currId] != 1 && noOfHits[i] < noOfHits[currId]) + { + if (AI_WhichMoveBetter(moves[currId], moves[i], battlerAtk, battlerDef) != 0) + score--; + } + } + + // If 2 moves can KO the target in the same number of turns, + // but one of them always hits and there is a risk the other + // move could miss, prioritize the move with better accuracy. + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (i != currId && noOfHits[currId] == noOfHits[i] + && CompareMoveAccuracies(battlerAtk, battlerDef, currId, i)) + score++; + } + } + + return score; +} + // AI_FLAG_CHECK_VIABILITY - a weird mix of increasing and decreasing scores static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { // move data u32 moveEffect = gBattleMoves[move].effect; struct AiLogicData *aiData = AI_DATA; - u32 effectiveness = aiData->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]; + u32 movesetIndex = AI_THINKING_STRUCT->movesetIndex; + u32 effectiveness = aiData->effectiveness[battlerAtk][battlerDef][movesetIndex]; s8 atkPriority = GetMovePriority(battlerAtk, move); u32 predictedMove = aiData->predictedMoves[battlerDef]; u32 predictedMoveSlot = GetMoveSlot(GetMovesArray(battlerDef), predictedMove); @@ -3154,6 +3268,9 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) return score; + if (gBattleMoves[move].power) + score += AI_CompareDamagingMoves(battlerAtk, battlerDef, movesetIndex); + // check always hits if (!IS_MOVE_STATUS(move) && gBattleMoves[move].accuracy == 0) { @@ -3162,7 +3279,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score { u32 mostDmgMoveId = GetAIMostDamagingMoveId(battlerAtk, battlerDef); u32 *dmgs = aiData->simulatedDmg[battlerAtk][battlerDef]; - if (GetNoOfHitsToKO(dmgs[mostDmgMoveId], gBattleMons[battlerDef].hp) == GetNoOfHitsToKO(dmgs[AI_THINKING_STRUCT->movesetIndex], gBattleMons[battlerDef].hp)) + if (GetNoOfHitsToKO(dmgs[mostDmgMoveId], gBattleMons[battlerDef].hp) == GetNoOfHitsToKO(dmgs[movesetIndex], gBattleMons[battlerDef].hp)) score++; } if (gBattleMons[battlerDef].statStages[STAT_EVASION] >= 10 || gBattleMons[battlerAtk].statStages[STAT_ACC] <= 2) @@ -3187,7 +3304,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score } // check damage - if (gBattleMoves[move].power != 0 && GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == MOVE_POWER_WEAK) + if (gBattleMoves[move].power != 0 && GetMoveDamageResult(battlerAtk, battlerDef, movesetIndex) == MOVE_POWER_WEAK) score--; // check status move preference @@ -3247,7 +3364,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score case ABILITY_AS_ONE_SHADOW_RIDER: 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, movesetIndex, 0)) score += 8; // prioritize killing target for stat boost } break; @@ -3716,7 +3833,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score case EFFECT_PARTING_SHOT: if (!IsDoubleBattle()) { - switch (ShouldPivot(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, AI_THINKING_STRUCT->movesetIndex)) + switch (ShouldPivot(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, movesetIndex)) { case 0: // no score -= 10; // technically should go in CheckBadMove, but this is easier/less computationally demanding @@ -3782,7 +3899,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score { u32 newHp = (gBattleMons[battlerAtk].hp + gBattleMons[battlerDef].hp) / 2; u32 healthBenchmark = (gBattleMons[battlerAtk].hp * 12) / 10; - if (newHp > healthBenchmark && ShouldAbsorb(battlerAtk, battlerDef, move, aiData->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex])) + if (newHp > healthBenchmark && ShouldAbsorb(battlerAtk, battlerDef, move, aiData->simulatedDmg[battlerAtk][battlerDef][movesetIndex])) score += 2; } break; @@ -4042,6 +4159,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score score++; if (HasMoveEffect(battlerDef, EFFECT_MORNING_SUN) || HasMoveEffect(battlerDef, EFFECT_SYNTHESIS) + || HasMoveEffect(battlerDef, EFFECT_SOLAR_BEAM) || HasMoveEffect(battlerDef, EFFECT_MOONLIGHT)) score += 2; if (HasMoveWithType(battlerDef, TYPE_FIRE) || HasMoveWithType(BATTLE_PARTNER(battlerDef), TYPE_FIRE)) @@ -4071,7 +4189,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score case EFFECT_FELL_STINGER: if (gBattleMons[battlerAtk].statStages[STAT_ATK] < MAX_STAT_STAGE && aiData->abilities[battlerAtk] != ABILITY_CONTRARY - && CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) + && CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, 0)) { if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker goes first score += 9; @@ -4837,9 +4955,12 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score IncreasePoisonScore(battlerAtk, battlerDef, move, &score); IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPEED, &score); break; + case EFFECT_SOLAR_BEAM: + if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, movesetIndex) >= 2 + && HasMoveEffect(battlerAtk, EFFECT_SUNNY_DAY) && (AI_GetWeather(aiData) & B_WEATHER_SUN)) // Use Sunny Day to boost damage. + score -= 3; case EFFECT_TWO_TURNS_ATTACK: case EFFECT_SKULL_BASH: - case EFFECT_SOLAR_BEAM: if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_POWER_HERB) score += 2; break; diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 0a65f40833..55ff27fbfb 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -881,14 +881,140 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes return dmg; } -// Checks if one of the moves has side effects or perks -static u32 WhichMoveBetter(u32 move1, u32 move2) +bool32 AI_IsDamagedByRecoil(u32 battler) { - s32 defAbility = AI_DATA->abilities[gBattlerTarget]; + u32 ability = AI_DATA->abilities[battler]; + if (ability == ABILITY_MAGIC_GUARD || ability == ABILITY_ROCK_HEAD) + return FALSE; + return TRUE; +} + +// Decide whether move having an additional effect for . +static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move) +{ + u32 i; + u32 abilityDef = AI_DATA->abilities[battlerDef]; + u32 abilityAtk = AI_DATA->abilities[battlerAtk]; + + switch (gBattleMoves[move].effect) + { + case EFFECT_HIT: + default: + return FALSE; + case EFFECT_PARALYZE_HIT: + if (AI_CanParalyze(battlerAtk, battlerDef, abilityDef, move, MOVE_NONE)) + return TRUE; + break; + case EFFECT_BURN_HIT: + if (AI_CanBurn(battlerAtk, battlerDef, abilityDef, BATTLE_PARTNER(battlerAtk), move, MOVE_NONE)) + return TRUE; + break; + case EFFECT_POISON_HIT: + case EFFECT_POISON_FANG: + if (AI_CanPoison(battlerAtk, battlerDef, abilityDef, move, MOVE_NONE)) + return TRUE; + break; + case EFFECT_FREEZE_HIT: + if (AI_CanGetFrostbite(battlerDef, abilityDef)) + return TRUE; + break; + case EFFECT_CONFUSE_HIT: + if (AI_CanConfuse(battlerAtk, battlerDef, abilityDef, BATTLE_PARTNER(battlerAtk), move, MOVE_NONE)) + return TRUE; + break; + case EFFECT_FLINCH_STATUS: + switch (gBattleMoves[move].argument) + { + case STATUS1_PARALYSIS: + if (AI_CanParalyze(battlerAtk, battlerDef, abilityDef, move, MOVE_NONE)) + return TRUE; + break; + case STATUS1_BURN: + if (AI_CanBurn(battlerAtk, battlerDef, abilityDef, BATTLE_PARTNER(battlerAtk), move, MOVE_NONE)) + return TRUE; + break; + case STATUS1_FREEZE: + if (AI_CanGetFrostbite(battlerDef, abilityDef)) + return TRUE; + break; + } + // fallthrough + case EFFECT_FLINCH_HIT: + if (ShouldTryToFlinch(battlerAtk, battlerDef, abilityAtk, abilityDef, move)) + return TRUE; + break; + case EFFECT_HIT_ESCAPE: + if (CountUsablePartyMons(battlerAtk) != 0 && ShouldPivot(battlerAtk, battlerDef, abilityDef, move, AI_THINKING_STRUCT->movesetIndex)) + return TRUE; + break; + case EFFECT_ATTACK_UP_HIT: + case EFFECT_FELL_STINGER: + if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ATK)) + return TRUE; + break; + case EFFECT_DEFENSE_UP2_HIT: + case EFFECT_DEFENSE_UP_HIT: + if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_DEF)) + return TRUE; + break; + case EFFECT_SPEED_UP_HIT: + if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPEED)) + return TRUE; + break; + case EFFECT_SPECIAL_ATTACK_UP_HIT: + if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPATK)) + return TRUE; + break; + case EFFECT_ATTACK_DOWN_HIT: + if (ShouldLowerStat(battlerDef, abilityDef, STAT_ATK) && abilityDef != ABILITY_HYPER_CUTTER) + return TRUE; + break; + case EFFECT_DEFENSE_DOWN_HIT: + if (ShouldLowerStat(battlerDef, abilityDef, STAT_DEF)) + return TRUE; + break; + case EFFECT_SPEED_DOWN_HIT: + if (ShouldLowerStat(battlerDef, abilityDef, STAT_SPEED)) + return TRUE; + break; + case EFFECT_SPECIAL_ATTACK_DOWN_HIT: + if (ShouldLowerStat(battlerDef, abilityDef, STAT_SPATK)) + return TRUE; + break; + case EFFECT_SPECIAL_DEFENSE_DOWN_HIT: + case EFFECT_SPECIAL_DEFENSE_DOWN_HIT_2: + if (ShouldLowerStat(battlerDef, abilityDef, STAT_SPDEF)) + return TRUE; + break; + case EFFECT_ACCURACY_DOWN_HIT: + if (ShouldLowerStat(battlerDef, abilityDef, STAT_ACC)) + return TRUE; + break; + case EFFECT_EVASION_DOWN_HIT: + if (ShouldLowerStat(battlerDef, abilityDef, STAT_EVASION)) + return TRUE; + break; + case EFFECT_ALL_STATS_UP_HIT: + for (i = STAT_ATK; i <= NUM_STATS; i++) + { + if (BattlerStatCanRise(battlerAtk, abilityAtk, i)) + return TRUE; + } + break; + } + + return FALSE; +} + +// Checks if one of the moves has side effects or perks, assuming equal dmg or equal no of hits to KO +u32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef) +{ + bool32 effect1, effect2; + s32 defAbility = AI_DATA->abilities[battlerDef]; // Check if physical moves hurt. - if (AI_DATA->holdEffects[sBattler_AI] != HOLD_EFFECT_PROTECTIVE_PADS - && (BATTLE_HISTORY->itemEffects[gBattlerTarget] == HOLD_EFFECT_ROCKY_HELMET + if (AI_DATA->holdEffects[battlerAtk] != HOLD_EFFECT_PROTECTIVE_PADS + && (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_ROCKY_HELMET || defAbility == ABILITY_IRON_BARBS || defAbility == ABILITY_ROUGH_SKIN)) { if (IS_MOVE_PHYSICAL(move1) && !IS_MOVE_PHYSICAL(move2)) @@ -897,11 +1023,10 @@ static u32 WhichMoveBetter(u32 move1, u32 move2) return 0; } // Check recoil - if (GetBattlerAbility(sBattler_AI) != ABILITY_ROCK_HEAD) + if (AI_IsDamagedByRecoil(battlerAtk)) { if (IS_MOVE_RECOIL(move1) && !IS_MOVE_RECOIL(move2) && gBattleMoves[move2].effect != EFFECT_RECHARGE) return 1; - if (IS_MOVE_RECOIL(move2) && !IS_MOVE_RECOIL(move1) && gBattleMoves[move1].effect != EFFECT_RECHARGE) return 0; } @@ -911,9 +1036,11 @@ static u32 WhichMoveBetter(u32 move1, u32 move2) if (gBattleMoves[move2].effect == EFFECT_RECHARGE && gBattleMoves[move1].effect != EFFECT_RECHARGE) return 0; // Check additional effect. - if (gBattleMoves[move1].effect == 0 && gBattleMoves[move2].effect != 0) + effect1 = AI_IsMoveEffectInPlus(battlerAtk, battlerDef, move1); + effect2 = AI_IsMoveEffectInPlus(battlerAtk, battlerDef, move2); + if (effect2 && !effect1) return 1; - if (gBattleMoves[move2].effect == 0 && gBattleMoves[move1].effect != 0) + if (effect1 && !effect2) return 0; return 2; @@ -921,10 +1048,38 @@ static u32 WhichMoveBetter(u32 move1, u32 move2) u32 GetNoOfHitsToKO(u32 dmg, s32 hp) { + if (dmg == 0) + return 0; return hp / (dmg + 1) + 1; } -void SetMoveDamageResult(u32 battlerAtk, u16 *moves) +u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef) +{ + return GetNoOfHitsToKO(dmg, gBattleMons[battlerDef].hp); +} + +u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex) +{ + return GetNoOfHitsToKOBattlerDmg(AI_DATA->simulatedDmg[battlerAtk][battlerDef][moveIndex], battlerDef); +} + +bool32 IsInIgnoredPowerfulMoveEffects(u32 effect) +{ + u32 i; + for (i = 0; sIgnoredPowerfulMoveEffects[i] != IGNORED_MOVES_END; i++) + { + if (effect == sIgnoredPowerfulMoveEffects[i]) + { + // Don't ingore Solar Beam if doesn't have a charging turn. + if (effect == EFFECT_SOLAR_BEAM && (AI_GetWeather(AI_DATA) & B_WEATHER_SUN)) + break; + return TRUE; + } + } + return FALSE; +} + +void SetMovesDamageResults(u32 battlerAtk, u16 *moves) { s32 i, j, battlerDef, bestId, currId, hp, result; s32 moveDmgs[MAX_MON_MOVES]; @@ -933,16 +1088,14 @@ void SetMoveDamageResult(u32 battlerAtk, u16 *moves) for (i = 0; i < MAX_MON_MOVES; i++) { u32 move = moves[i]; - for (j = 0; sIgnoredPowerfulMoveEffects[j] != IGNORED_MOVES_END; j++) - { - if (gBattleMoves[move].effect == sIgnoredPowerfulMoveEffects[j]) - break; - } - if (move == 0 || move == 0xFFFF || gBattleMoves[move].power == 0 || sIgnoredPowerfulMoveEffects[j] != IGNORED_MOVES_END) + if (move == MOVE_NONE || move == MOVE_UNAVAILABLE || gBattleMoves[move].power == 0 || IsInIgnoredPowerfulMoveEffects(gBattleMoves[move].effect)) isNotConsidered[i] = TRUE; else isNotConsidered[i] = FALSE; + } + for (i = 0; i < MAX_MON_MOVES; i++) + { for (battlerDef = 0; battlerDef < MAX_BATTLERS_COUNT; battlerDef++) { if (battlerDef == battlerAtk) @@ -950,7 +1103,7 @@ void SetMoveDamageResult(u32 battlerAtk, u16 *moves) if (isNotConsidered[i]) { - AI_DATA->moveDmgResult[battlerAtk][battlerDef][i] = MOVE_POWER_OTHER; // Move has a power of 0/1, or is in the group sIgnoredPowerfulMoveEffects + result = MOVE_POWER_OTHER; // Move has a power of 0/1, or is in the group sIgnoredPowerfulMoveEffects } else { @@ -972,13 +1125,14 @@ void SetMoveDamageResult(u32 battlerAtk, u16 *moves) moveDmgs[j] = hp; } + // Find move which deals most damage, in case of a tie prioritize one with better effect. for (bestId = 0, j = 1; j < MAX_MON_MOVES; j++) { if (moveDmgs[j] > moveDmgs[bestId]) bestId = j; if (moveDmgs[j] == moveDmgs[bestId]) { - switch (WhichMoveBetter(gBattleMons[battlerAtk].moves[bestId], gBattleMons[battlerAtk].moves[j])) + switch (AI_WhichMoveBetter(gBattleMons[battlerAtk].moves[bestId], gBattleMons[battlerAtk].moves[j], battlerAtk, battlerDef)) { case 2: if (Random() & 1) @@ -995,13 +1149,12 @@ void SetMoveDamageResult(u32 battlerAtk, u16 *moves) result = MOVE_POWER_BEST; else if ((moveDmgs[currId] >= hp || moveDmgs[bestId] < hp) // If current move can faint as well, or if neither can && GetNoOfHitsToKO(moveDmgs[currId], hp) - GetNoOfHitsToKO(moveDmgs[bestId], hp) <= 2 // Consider a move weak if it needs to be used at least 2 times more to faint the target, compared to the best move. - && WhichMoveBetter(gBattleMons[battlerAtk].moves[bestId], gBattleMons[battlerAtk].moves[currId]) != 0) + && AI_WhichMoveBetter(gBattleMons[battlerAtk].moves[bestId], gBattleMons[battlerAtk].moves[currId], battlerAtk, battlerDef) != 0) result = MOVE_POWER_GOOD; else result = MOVE_POWER_WEAK; - - AI_DATA->moveDmgResult[battlerAtk][battlerDef][i] = result; } + AI_DATA->moveDmgResult[battlerAtk][battlerDef][i] = result; } } } @@ -1216,7 +1369,7 @@ bool32 AI_IsAbilityOnSide(u32 battlerId, u32 ability) } // does NOT include ability suppression checks -s32 AI_GetAbility(u32 battlerId) +s32 AI_DecideKnownAbilityForTurn(u32 battlerId) { u32 knownAbility = GetBattlerAbility(battlerId); @@ -1254,7 +1407,7 @@ s32 AI_GetAbility(u32 battlerId) return ABILITY_NONE; // Unknown. } -u32 AI_GetHoldEffect(u32 battlerId) +u32 AI_DecideHoldEffectForTurn(u32 battlerId) { u32 holdEffect; diff --git a/src/battle_debug.c b/src/battle_debug.c index 92c1082383..110a6c1165 100644 --- a/src/battle_debug.c +++ b/src/battle_debug.c @@ -857,9 +857,9 @@ static void PutAiInfoText(struct BattleDebugMenu *data) { if (GetBattlerSide(i) == B_SIDE_PLAYER && IsBattlerAlive(i)) { - u16 ability = AI_GetAbility(i); - u16 holdEffect = AI_GetHoldEffect(i); - u16 item = gBattleMons[i].item; + u16 ability = AI_DATA->abilities[i]; + u16 holdEffect = AI_DATA->holdEffects[i]; + u16 item = AI_DATA->items[i]; u8 x = (i == B_POSITION_PLAYER_LEFT) ? 83 + (i) * 75 : 83 + (i-1) * 75; AddTextPrinterParameterized(data->aiMovesWindowId, 0, gAbilityNames[ability], x, 0, 0, NULL); AddTextPrinterParameterized(data->aiMovesWindowId, 0, ItemId_GetName(item), x, 15, 0, NULL); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index c4badc5a50..41749841a3 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1625,10 +1625,10 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u { u32 calc, moveAcc; s8 buff, accStage, evasionStage; - u8 atkParam = GetBattlerHoldEffectParam(battlerAtk); - u8 defParam = GetBattlerHoldEffectParam(battlerDef); - u8 atkAlly = BATTLE_PARTNER(battlerAtk); - u16 atkAllyAbility = GetBattlerAbility(atkAlly); + u32 atkParam = GetBattlerHoldEffectParam(battlerAtk); + u32 defParam = GetBattlerHoldEffectParam(battlerDef); + u32 atkAlly = BATTLE_PARTNER(battlerAtk); + u32 atkAllyAbility = GetBattlerAbility(atkAlly); gPotentialItemEffectBattler = battlerDef; accStage = gBattleMons[battlerAtk].statStages[STAT_ACC];