diff --git a/include/battle.h b/include/battle.h index 173c16c764..53421d368a 100644 --- a/include/battle.h +++ b/include/battle.h @@ -289,6 +289,8 @@ struct AiLogicData u16 predictedMoves[MAX_BATTLERS_COUNT]; u8 hpPercents[MAX_BATTLERS_COUNT]; u16 partnerMove; + u16 speedStats[MAX_BATTLERS_COUNT]; // Speed stats for all battles, calculated only once, same way as damages + 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 moveLimitations[MAX_BATTLERS_COUNT]; diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index f97569c97e..5bacc3e192 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -7,6 +7,8 @@ #define FOE(battler) ((BATTLE_OPPOSITE(battler)) & BIT_SIDE) +#define AI_STRIKES_FIRST(battlerAi, battlerDef, move)((AI_WhoStrikesFirst(battlerAi, battlerDef, move) == AI_IS_FASTER)) + bool32 AI_RandLessThan(u8 val); bool32 IsAiVsAiBattle(void); bool32 BattlerHasAi(u32 battlerId); @@ -23,13 +25,12 @@ void SetBattlerData(u32 battlerId); void RestoreBattlerData(u32 battlerId); u16 GetAIChosenMove(u8 battlerId); -bool32 WillAIStrikeFirst(void); u32 GetTotalBaseStat(u32 species); 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, u16 consideredMove); +u32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 moveConsidered); bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk); bool32 CanMoveFaintBattler(u16 move, u32 battlerDef, u32 battlerAtk, u8 nHits); bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dmgMod); @@ -87,7 +88,8 @@ bool32 MovesWithSplitUnusable(u32 attacker, u32 target, u32 split); 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 GetNoOfHitsToKO(u32 dmg, s32 hp); -u8 GetMoveDamageResult(u16 move); +void SetMoveDamageResult(u32 battlerAtk, u16 *moves); +u32 GetMoveDamageResult(u32 battlerAtk, u32 battlerDef, u32 moveIndex); u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef); uq4_12_t AI_GetTypeEffectiveness(u16 move, u32 battlerAtk, u32 battlerDef); u32 AI_GetMoveEffectiveness(u16 move, u32 battlerAtk, u32 battlerDef); diff --git a/include/battle_main.h b/include/battle_main.h index 6b7756012f..5d1fe6bbfa 100644 --- a/include/battle_main.h +++ b/include/battle_main.h @@ -59,10 +59,13 @@ void BattleTurnPassed(void); u8 IsRunningFromBattleImpossible(u32 battler); void SwitchPartyOrder(u8 battlerId); void SwapTurnOrder(u8 id1, u8 id2); -u32 GetBattlerTotalSpeedStat(u8 battlerId); +u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, u32 holdEffect); +u32 GetBattlerTotalSpeedStat(u32 battler); s8 GetChosenMovePriority(u32 battlerId); s8 GetMovePriority(u32 battlerId, u16 move); -u8 GetWhoStrikesFirst(u8 battlerId1, u8 battlerId2, bool8 ignoreChosenMoves); +u32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMoves, u32 ability1, u32 ability2, + u32 holdEffectBattler1, u32 holdEffectBattler2, u32 speedBattler1, u32 speedBattler2, s32 priority1, s32 priority2); +u32 GetWhichBattlerFaster(u32 battler1, u32 battler2, bool32 ignoreChosenMoves); void RunBattleScriptCommands_PopCallbacksStack(void); void RunBattleScriptCommands(void); void SpecialStatusesClear(void); diff --git a/include/battle_util.h b/include/battle_util.h index 016499362b..16b33ad0ec 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -167,7 +167,7 @@ u32 GetBattlerHoldEffectParam(u32 battler); bool32 IsMoveMakingContact(u32 move, u32 battlerAtk); bool32 IsBattlerGrounded(u32 battler); bool32 IsBattlerAlive(u32 battler); -u32 GetBattleMonMoveSlot(struct BattlePokemon *battleMon, u32 move); +u32 GetMoveSlot(u16 *moves, u32 move); u32 GetBattlerWeight(u32 battler); u32 CalcRolloutBasePower(u32 battlerAtk, u32 basePower, u32 rolloutTimer); u32 CalcFuryCutterBasePower(u32 basePower, u32 furyCutterCounter); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 3e811e3347..4d518d9266 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -338,18 +338,55 @@ void Ai_UpdateFaintData(u32 battler) static void SetBattlerAiData(u32 battler) { - AI_DATA->abilities[battler] = AI_GetAbility(battler); + u32 ability, holdEffect; + + ability = AI_DATA->abilities[battler] = AI_GetAbility(battler); AI_DATA->items[battler] = gBattleMons[battler].item; - AI_DATA->holdEffects[battler] = AI_GetHoldEffect(battler); + holdEffect = AI_DATA->holdEffects[battler] = AI_GetHoldEffect(battler); AI_DATA->holdEffectParams[battler] = GetBattlerHoldEffectParam(battler); AI_DATA->predictedMoves[battler] = gLastMoves[battler]; AI_DATA->hpPercents[battler] = GetHealthPercentage(battler); AI_DATA->moveLimitations[battler] = CheckMoveLimitations(battler, 0, MOVE_LIMITATIONS_ALL); + AI_DATA->speedStats[battler] = GetBattlerTotalSpeedStatArgs(battler, ability, holdEffect); +} + +static void SetBattlerAiMovesData(u32 battlerAtk, u32 battlersCount) +{ + u32 battlerDef, i; + u16 *moves; + + // Simulate dmg for both ai controlled mons and for player controlled mons. + SaveBattlerData(battlerAtk); + moves = GetMovesArray(battlerAtk); + for (battlerDef = 0; battlerDef < battlersCount; battlerDef++) + { + if (battlerAtk == battlerDef) + continue; + + SaveBattlerData(battlerDef); + for (i = 0; i < MAX_MON_MOVES; i++) + { + s32 dmg = 0; + u8 effectiveness = AI_EFFECTIVENESS_x0; + u32 move = moves[i]; + + 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, TRUE); + } + + AI_DATA->simulatedDmg[battlerAtk][battlerDef][i] = dmg; + AI_DATA->effectiveness[battlerAtk][battlerDef][i] = effectiveness; + } + } + SetMoveDamageResult(battlerAtk, moves); } void SetAiLogicDataForTurn(void) { - u32 battlerAtk, battlerDef, i, battlersCount; + u32 battlerAtk, battlersCount; memset(AI_DATA, 0, sizeof(struct AiLogicData)); @@ -366,33 +403,7 @@ void SetAiLogicDataForTurn(void) continue; SetBattlerAiData(battlerAtk); - if (!IsAiBattlerAware(battlerAtk)) - continue; - - SaveBattlerData(battlerAtk); - for (battlerDef = 0; battlerDef < battlersCount; battlerDef++) - { - if (battlerAtk == battlerDef) - continue; - - SaveBattlerData(battlerDef); - for (i = 0; i < MAX_MON_MOVES; i++) - { - s32 dmg = 0; - u8 effectiveness = AI_EFFECTIVENESS_x0; - u32 move = gBattleMons[battlerAtk].moves[i]; - - 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, TRUE); - } - - AI_DATA->simulatedDmg[battlerAtk][battlerDef][i] = dmg; - AI_DATA->effectiveness[battlerAtk][battlerDef][i] = effectiveness; - } - } + SetBattlerAiMovesData(battlerAtk, battlersCount); } } @@ -715,6 +726,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u16 move, s32 score) u32 i; u16 predictedMove = AI_DATA->predictedMoves[battlerDef]; + SetTypeBeforeUsingMove(move, battlerAtk); GET_MOVE_TYPE(move, moveType); @@ -2616,7 +2628,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u16 move, s32 score) score--; break; case EFFECT_VITAL_THROW: - if (WillAIStrikeFirst() && AI_DATA->hpPercents[battlerAtk] < 40) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, move) && AI_DATA->hpPercents[battlerAtk] < 40) score--; // don't want to move last break; case EFFECT_FLAIL: @@ -2688,16 +2700,20 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u16 move, s32 score) static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u16 move, s32 score) { + u32 movesetIndex = AI_THINKING_STRUCT->movesetIndex; + bool32 aiFaster; + if (IsTargetingPartner(battlerAtk, battlerDef)) return score; if (gBattleMoves[move].power == 0) return score; // can't make anything faint with no power - if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0) && gBattleMoves[move].effect != EFFECT_EXPLOSION) + aiFaster = AI_STRIKES_FIRST(battlerAtk, battlerDef, move); + if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, 0) && gBattleMoves[move].effect != EFFECT_EXPLOSION) { // this move can faint the target - if (WillAIStrikeFirst() || GetMovePriority(battlerAtk, move) > 0) + if (aiFaster || GetMovePriority(battlerAtk, move) > 0) score += 4; // we go first or we're using priority move else score += 2; @@ -2708,10 +2724,10 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u16 move, s32 score) if (gBattleMoves[move].highCritRatio) score += 2; // crit makes it more likely to make them faint - if (GetMoveDamageResult(move) == MOVE_POWER_OTHER) + if (GetMoveDamageResult(battlerAtk, battlerDef, movesetIndex) == MOVE_POWER_OTHER) score--; - switch (AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]) + switch (AI_DATA->effectiveness[battlerAtk][battlerDef][movesetIndex]) { case AI_EFFECTIVENESS_x8: score += 8; @@ -2729,9 +2745,9 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u16 move, s32 score) } //AI_TryToFaint_CheckIfDanger - if (!WillAIStrikeFirst() && CanTargetFaintAi(battlerDef, battlerAtk)) + if (!aiFaster && CanTargetFaintAi(battlerDef, battlerAtk)) { // AI_TryToFaint_Danger - if (GetMoveDamageResult(move) != MOVE_POWER_BEST) + if (GetMoveDamageResult(battlerAtk, battlerDef, movesetIndex) != MOVE_POWER_BEST) score--; else score++; @@ -2793,7 +2809,6 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u16 move, s32 score) } } // check partner move effect - // consider our move effect relative to partner state switch (effect) { @@ -2815,7 +2830,6 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u16 move, s32 score) break; } // our effect relative to partner - // consider global move effects switch (effect) { @@ -2853,11 +2867,10 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u16 move, s32 score) break; } // global move effect check - // check specific target if (IsTargetingPartner(battlerAtk, battlerDef)) { - if (GetMoveDamageResult(move) == MOVE_POWER_OTHER) + if (GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == MOVE_POWER_OTHER) { // partner ability checks if (!partnerProtecting && moveTarget != MOVE_TARGET_BOTH && !DoesBattlerIgnoreAbilityChecks(AI_DATA->abilities[battlerAtk], move)) @@ -2900,7 +2913,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u16 move, s32 score) } break; case ABILITY_WATER_COMPACTION: - if (moveType == TYPE_WATER && GetMoveDamageResult(move) == MOVE_POWER_WEAK) + if (moveType == TYPE_WATER && GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == MOVE_POWER_WEAK) { RETURN_SCORE_PLUS(1); // only mon with this ability is weak to water so only make it okay if we do very little damage } @@ -3161,6 +3174,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score u32 effectiveness = AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]; u8 atkPriority = GetMovePriority(battlerAtk, move); u16 predictedMove = AI_DATA->predictedMoves[battlerDef]; + u32 predictedMoveSlot = GetMoveSlot(GetMovesArray(battlerDef), predictedMove); bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk); u32 i; // We only check for moves that have a 20% chance or more for their secondary effect to happen because moves with a smaller chance are rather worthless. We don't want the AI to use those. @@ -3203,7 +3217,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score } // check damage - if (gBattleMoves[move].power != 0 && GetMoveDamageResult(move) == MOVE_POWER_WEAK) + if (gBattleMoves[move].power != 0 && GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == MOVE_POWER_WEAK) score--; // check status move preference @@ -3334,7 +3348,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score break; case EFFECT_SPEED_UP: case EFFECT_SPEED_UP_2: - if (!WillAIStrikeFirst()) + if (!AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) { if (!AI_RandLessThan(70)) score += 3; @@ -3432,7 +3446,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score break; case EFFECT_SPEED_DOWN: case EFFECT_SPEED_DOWN_2: - if (WillAIStrikeFirst()) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) score -= 3; else if (!AI_RandLessThan(70)) score += 2; @@ -3675,7 +3689,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score score += 2; break; case EFFECT_SPEED_DOWN_HIT: - if (WillAIStrikeFirst()) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) score -= 2; else if (!AI_RandLessThan(70)) score++; @@ -3819,7 +3833,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score score++; break; case EFFECT_SPEED_UP_HIT: - if (sereneGraceBoost && AI_DATA->abilities[battlerDef] != ABILITY_CONTRARY && !WillAIStrikeFirst()) + if (sereneGraceBoost && AI_DATA->abilities[battlerDef] != ABILITY_CONTRARY && !AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) score += 3; break; case EFFECT_DESTINY_BOND: @@ -4432,7 +4446,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score if (IsStatBoostingBerry(item) && AI_DATA->hpPercents[battlerAtk] > 60) score++; else if (ShouldRestoreHpBerry(battlerAtk, item) && !CanAIFaintTarget(battlerAtk, battlerDef, 0) - && ((GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 && CanTargetFaintAiWithMod(battlerDef, battlerAtk, 0, 0)) + && ((GetWhichBattlerFaster(battlerAtk, battlerDef, TRUE) == 0 && CanTargetFaintAiWithMod(battlerDef, battlerAtk, 0, 0)) || !CanTargetFaintAiWithMod(battlerDef, battlerAtk, toHeal, 0))) score++; // Recycle healing berry if we can't otherwise faint the target and the target wont kill us after we activate the berry } @@ -4865,7 +4879,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score { if (gDisableStructs[battlerDef].tauntTimer != 0) score++; // target must use damaging move - if (GetMoveDamageResult(predictedMove) >= MOVE_POWER_GOOD && GetBattleMoveSplit(predictedMove) == SPLIT_PHYSICAL) + if (GetMoveDamageResult(battlerDef, battlerAtk, predictedMoveSlot) >= MOVE_POWER_GOOD && GetBattleMoveSplit(predictedMove) == SPLIT_PHYSICAL) score += 3; } break; @@ -4874,7 +4888,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score { if (gDisableStructs[battlerDef].tauntTimer != 0) score++; // target must use damaging move - if (GetMoveDamageResult(predictedMove) >= MOVE_POWER_GOOD && GetBattleMoveSplit(predictedMove) == SPLIT_SPECIAL) + if (GetMoveDamageResult(battlerDef, battlerAtk, predictedMoveSlot) >= MOVE_POWER_GOOD && GetBattleMoveSplit(predictedMove) == SPLIT_SPECIAL) score += 3; } break; @@ -5111,7 +5125,7 @@ static s32 AI_PreferStrongestMove(u32 battlerAtk, u32 battlerDef, u16 move, s32 if (IsTargetingPartner(battlerAtk, battlerDef)) return score; - if (GetMoveDamageResult(move) == MOVE_POWER_BEST) + if (GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == MOVE_POWER_BEST) score += 2; return score; @@ -5124,7 +5138,7 @@ static s32 AI_PreferBatonPass(u32 battlerAtk, u32 battlerDef, u16 move, s32 scor if (IsTargetingPartner(battlerAtk, battlerDef) || CountUsablePartyMons(battlerAtk) == 0 - || GetMoveDamageResult(move) != MOVE_POWER_OTHER + || GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) != MOVE_POWER_OTHER || !HasMoveEffect(battlerAtk, EFFECT_BATON_PASS) || IsBattlerTrapped(battlerAtk, TRUE)) return score; diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 1c12ecdfdb..b2f56326f7 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -400,7 +400,7 @@ static bool8 ShouldSwitchIfGameStatePrompt(u32 battler) && AnyStatIsRaised(battler)) switchMon = FALSE; if (AiExpectsToFaintPlayer(battler) - && !WillAIStrikeFirst() + && !AI_STRIKES_FIRST(battler, opposingBattler, 0) && !AI_OpponentCanFaintAiWithMod(battler, 0)) switchMon = FALSE; } diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 6e85112c21..d2c7e574d9 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -424,11 +424,6 @@ u16 GetAIChosenMove(u8 battlerId) return (gBattleMons[battlerId].moves[gBattleStruct->aiMoveOrAction[battlerId]]); } -bool32 WillAIStrikeFirst(void) -{ - return (AI_WhoStrikesFirst(sBattler_AI, gBattlerTarget, AI_THINKING_STRUCT->moveConsidered) == AI_IS_FASTER); -} - bool32 AI_RandLessThan(u8 val) { if ((Random() % 0xFF) < val) @@ -945,85 +940,91 @@ u32 GetNoOfHitsToKO(u32 dmg, s32 hp) return hp / (dmg + 1) + 1; } -u8 GetMoveDamageResult(u16 move) +void SetMoveDamageResult(u32 battlerAtk, u16 *moves) { - s32 i, checkedMove, bestId, currId, hp; + s32 i, j, battlerDef, bestId, currId, hp, result; s32 moveDmgs[MAX_MON_MOVES]; - u8 result; + bool8 isNotConsidered[MAX_MON_MOVES]; - for (i = 0; sIgnoredPowerfulMoveEffects[i] != IGNORED_MOVES_END; i++) + for (i = 0; i < MAX_MON_MOVES; i++) { - if (gBattleMoves[move].effect == sIgnoredPowerfulMoveEffects[i]) - break; - } - - if (gBattleMoves[move].power != 0 && sIgnoredPowerfulMoveEffects[i] == IGNORED_MOVES_END) - { - // Considered move has power and is not in sIgnoredPowerfulMoveEffects - // Check all other moves and calculate their power - for (checkedMove = 0; checkedMove < MAX_MON_MOVES; checkedMove++) + u16 move = moves[i]; + for (j = 0; sIgnoredPowerfulMoveEffects[j] != IGNORED_MOVES_END; j++) { - for (i = 0; sIgnoredPowerfulMoveEffects[i] != IGNORED_MOVES_END; i++) - { - if (gBattleMoves[gBattleMons[sBattler_AI].moves[checkedMove]].effect == sIgnoredPowerfulMoveEffects[i]) - break; - } + if (gBattleMoves[move].effect == sIgnoredPowerfulMoveEffects[j]) + break; + } + if (move == 0 || move == 0xFFFF || gBattleMoves[move].power == 0 || sIgnoredPowerfulMoveEffects[j] != IGNORED_MOVES_END) + isNotConsidered[i] = TRUE; + else + isNotConsidered[i] = FALSE; - if (gBattleMons[sBattler_AI].moves[checkedMove] != MOVE_NONE - && sIgnoredPowerfulMoveEffects[i] == IGNORED_MOVES_END - && gBattleMoves[gBattleMons[sBattler_AI].moves[checkedMove]].power != 0) + for (battlerDef = 0; battlerDef < MAX_BATTLERS_COUNT; battlerDef++) + { + if (battlerDef == battlerAtk) + continue; + + if (isNotConsidered[i]) { - moveDmgs[checkedMove] = AI_DATA->simulatedDmg[sBattler_AI][gBattlerTarget][checkedMove]; + AI_DATA->moveDmgResult[battlerAtk][battlerDef][i] = MOVE_POWER_OTHER; // Move has a power of 0/1, or is in the group sIgnoredPowerfulMoveEffects } else { - moveDmgs[checkedMove] = 0; - } - } - - hp = gBattleMons[gBattlerTarget].hp + (20 * gBattleMons[gBattlerTarget].hp / 100); // 20 % add to make sure the battler is always fainted - // If a move can faint battler, it doesn't matter how much damage it does - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (moveDmgs[i] > hp) - moveDmgs[i] = hp; - } - - for (bestId = 0, i = 1; i < MAX_MON_MOVES; i++) - { - if (moveDmgs[i] > moveDmgs[bestId]) - bestId = i; - if (moveDmgs[i] == moveDmgs[bestId]) - { - switch (WhichMoveBetter(gBattleMons[sBattler_AI].moves[bestId], gBattleMons[sBattler_AI].moves[i])) + // Considered move has power and is not in sIgnoredPowerfulMoveEffects + // Check all other moves and calculate their power + for (j = 0; j < MAX_MON_MOVES; j++) { - case 2: - if (Random() & 1) - break; - case 1: - bestId = i; - break; + if (!isNotConsidered[j]) + moveDmgs[j] = AI_DATA->simulatedDmg[battlerAtk][battlerDef][j]; + else + moveDmgs[j] = 0; } + + hp = gBattleMons[battlerDef].hp + (20 * gBattleMons[battlerDef].hp / 100); // 20 % add to make sure the battler is always fainted + // If a move can faint battler, it doesn't matter how much damage it does + for (j = 0; j < MAX_MON_MOVES; j++) + { + if (moveDmgs[j] > hp) + moveDmgs[j] = hp; + } + + 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])) + { + case 2: + if (Random() & 1) + break; + case 1: + bestId = j; + break; + } + } + } + + currId = i; + if (currId == bestId) + 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) + result = MOVE_POWER_GOOD; + else + result = MOVE_POWER_WEAK; + + AI_DATA->moveDmgResult[battlerAtk][battlerDef][i] = result; } } - - currId = AI_THINKING_STRUCT->movesetIndex; - if (currId == bestId) - AI_THINKING_STRUCT->funcResult = 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[sBattler_AI].moves[bestId], gBattleMons[sBattler_AI].moves[currId]) != 0) - AI_THINKING_STRUCT->funcResult = MOVE_POWER_GOOD; - else - AI_THINKING_STRUCT->funcResult = MOVE_POWER_WEAK; - } - else - { - // Move has a power of 0/1, or is in the group sIgnoredPowerfulMoveEffects - AI_THINKING_STRUCT->funcResult = MOVE_POWER_OTHER; } +} - return AI_THINKING_STRUCT->funcResult; +u32 GetMoveDamageResult(u32 battlerAtk, u32 battlerDef, u32 moveIndex) +{ + return AI_DATA->moveDmgResult[battlerAtk][battlerDef][moveIndex]; } u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef) @@ -1090,7 +1091,7 @@ static u32 AI_GetEffectiveness(uq4_12_t multiplier) * AI_IS_FASTER: is user(ai) faster * AI_IS_SLOWER: is target faster */ -u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2, u16 moveConsidered) +u32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 moveConsidered) { u32 fasterAI = 0, fasterPlayer = 0, i; s8 prioAI = 0; @@ -1126,7 +1127,11 @@ u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2, u16 moveConsidered) 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) + if (GetWhichBattlerFasterArgs(battlerAI, battler2, TRUE, + AI_DATA->abilities[battlerAI], AI_DATA->abilities[battler2], + AI_DATA->holdEffects[battlerAI], AI_DATA->holdEffects[battler2], + AI_DATA->speedStats[battlerAI], AI_DATA->speedStats[battler2], + prioAI, prioBattler2) == 0) return AI_IS_FASTER; else return AI_IS_SLOWER; @@ -1795,7 +1800,7 @@ u32 CountNegativeStatStages(u8 battlerId) bool32 ShouldLowerAttack(u32 battlerAtk, u32 battlerDef, u16 defAbility) { - if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_ATK] > 4 @@ -1812,7 +1817,7 @@ bool32 ShouldLowerAttack(u32 battlerAtk, u32 battlerDef, u16 defAbility) bool32 ShouldLowerDefense(u32 battlerAtk, u32 battlerDef, u16 defAbility) { - if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_DEF] > 4 @@ -1829,10 +1834,10 @@ bool32 ShouldLowerDefense(u32 battlerAtk, u32 battlerDef, u16 defAbility) bool32 ShouldLowerSpeed(u32 battlerAtk, u32 battlerDef, u16 defAbility) { - if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. - if (!WillAIStrikeFirst() + if (!AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && defAbility != ABILITY_CONTRARY && defAbility != ABILITY_CLEAR_BODY && defAbility != ABILITY_FULL_METAL_BODY @@ -1844,7 +1849,7 @@ bool32 ShouldLowerSpeed(u32 battlerAtk, u32 battlerDef, u16 defAbility) bool32 ShouldLowerSpAtk(u32 battlerAtk, u32 battlerDef, u16 defAbility) { - if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_SPATK] > 4 @@ -1860,7 +1865,7 @@ bool32 ShouldLowerSpAtk(u32 battlerAtk, u32 battlerDef, u16 defAbility) bool32 ShouldLowerSpDef(u32 battlerAtk, u32 battlerDef, u16 defAbility) { - if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_SPDEF] > 4 @@ -1876,7 +1881,7 @@ bool32 ShouldLowerSpDef(u32 battlerAtk, u32 battlerDef, u16 defAbility) bool32 ShouldLowerAccuracy(u32 battlerAtk, u32 battlerDef, u16 defAbility) { - if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (defAbility != ABILITY_CONTRARY @@ -1891,7 +1896,7 @@ bool32 ShouldLowerAccuracy(u32 battlerAtk, u32 battlerDef, u16 defAbility) bool32 ShouldLowerEvasion(u32 battlerAtk, u32 battlerDef, u16 defAbility) { - if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_EVASION] > DEFAULT_STAT_STAGE @@ -3637,7 +3642,7 @@ void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u8 statId, s32 *score) } break; case STAT_SPEED: - if (!WillAIStrikeFirst()) + if (!AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered)) { if (gBattleMons[battlerAtk].statStages[STAT_SPEED] < STAT_UP_2_STAGE) *score += 2; diff --git a/src/battle_main.c b/src/battle_main.c index 6fbbec8a15..89fbb8e493 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3771,7 +3771,7 @@ static void TryDoEventsBeforeFirstTurn(void) { for (j = i + 1; j < gBattlersCount; j++) { - if (GetWhoStrikesFirst(gBattlerByTurnOrder[i], gBattlerByTurnOrder[j], TRUE) != 0) + if (GetWhichBattlerFaster(gBattlerByTurnOrder[i], gBattlerByTurnOrder[j], TRUE) != 0) SwapTurnOrder(i, j); } } @@ -4598,11 +4598,10 @@ void SwapTurnOrder(u8 id1, u8 id2) SWAP(gBattlerByTurnOrder[id1], gBattlerByTurnOrder[id2], temp); } -u32 GetBattlerTotalSpeedStat(u8 battler) +// For AI, so it doesn't 'cheat' by knowing player's ability +u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, u32 holdEffect) { u32 speed = gBattleMons[battler].speed; - u32 ability = GetBattlerAbility(battler); - u32 holdEffect = GetBattlerHoldEffect(battler, TRUE); u32 highestStat = GetHighestStatId(battler); // weather abilities @@ -4669,6 +4668,13 @@ u32 GetBattlerTotalSpeedStat(u8 battler) return speed; } +u32 GetBattlerTotalSpeedStat(u32 battler) +{ + u32 ability = GetBattlerAbility(battler); + u32 holdEffect = GetBattlerHoldEffect(battler, TRUE); + return GetBattlerTotalSpeedStatArgs(battler, ability, holdEffect); +} + s8 GetChosenMovePriority(u32 battler) { u16 move; @@ -4733,17 +4739,13 @@ s8 GetMovePriority(u32 battler, u16 move) return priority; } -u8 GetWhoStrikesFirst(u8 battler1, u8 battler2, bool8 ignoreChosenMoves) +// Function for AI with variables provided as arguments to speed the computation time +u32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMoves, u32 ability1, u32 ability2, + u32 holdEffectBattler1, u32 holdEffectBattler2, u32 speedBattler1, u32 speedBattler2, s32 priority1, s32 priority2) { - u8 strikesFirst = 0; - u32 speedBattler1 = 0, speedBattler2 = 0; - u32 holdEffectBattler1 = 0, holdEffectBattler2 = 0; - s8 priority1 = 0, priority2 = 0; - u16 ability1 = GetBattlerAbility(battler1), ability2 = GetBattlerAbility(battler2); + u32 strikesFirst = 0; // Battler 1 - speedBattler1 = GetBattlerTotalSpeedStat(battler1); - holdEffectBattler1 = GetBattlerHoldEffect(battler1, TRUE); // Quick Draw if (!ignoreChosenMoves && ability1 == ABILITY_QUICK_DRAW && !IS_MOVE_STATUS(gChosenMoveByBattler[battler1]) && Random() % 100 < 30) gProtectStructs[battler1].quickDraw = TRUE; @@ -4754,8 +4756,6 @@ u8 GetWhoStrikesFirst(u8 battler1, u8 battler2, bool8 ignoreChosenMoves) gProtectStructs[battler1].usedCustapBerry = TRUE; // Battler 2 - speedBattler2 = GetBattlerTotalSpeedStat(battler2); - holdEffectBattler2 = GetBattlerHoldEffect(battler2, TRUE); // Quick Draw if (!ignoreChosenMoves && ability2 == ABILITY_QUICK_DRAW && !IS_MOVE_STATUS(gChosenMoveByBattler[battler2]) && Random() % 100 < 30) gProtectStructs[battler2].quickDraw = TRUE; @@ -4765,14 +4765,6 @@ u8 GetWhoStrikesFirst(u8 battler1, u8 battler2, bool8 ignoreChosenMoves) || (holdEffectBattler2 == HOLD_EFFECT_CUSTAP_BERRY && HasEnoughHpToEatBerry(battler2, 4, gBattleMons[battler2].item)))) gProtectStructs[battler2].usedCustapBerry = TRUE; - if (!ignoreChosenMoves) - { - if (gChosenActionByBattler[battler1] == B_ACTION_USE_MOVE) - priority1 = GetChosenMovePriority(battler1); - if (gChosenActionByBattler[battler2] == B_ACTION_USE_MOVE) - priority2 = GetChosenMovePriority(battler2); - } - if (priority1 == priority2) { // QUICK CLAW / CUSTAP - always first @@ -4835,6 +4827,28 @@ u8 GetWhoStrikesFirst(u8 battler1, u8 battler2, bool8 ignoreChosenMoves) return strikesFirst; } +u32 GetWhichBattlerFaster(u32 battler1, u32 battler2, bool32 ignoreChosenMoves) +{ + s32 priority1 = 0, priority2 = 0; + u32 ability1 = GetBattlerAbility(battler1); + u32 speedBattler1 = GetBattlerTotalSpeedStat(battler1); + u32 holdEffectBattler1 = GetBattlerHoldEffect(battler1, TRUE); + u32 speedBattler2 = GetBattlerTotalSpeedStat(battler2); + u32 holdEffectBattler2 = GetBattlerHoldEffect(battler2, TRUE); + u32 ability2 = GetBattlerAbility(battler2); + + if (!ignoreChosenMoves) + { + if (gChosenActionByBattler[battler1] == B_ACTION_USE_MOVE) + priority1 = GetChosenMovePriority(battler1); + if (gChosenActionByBattler[battler2] == B_ACTION_USE_MOVE) + priority2 = GetChosenMovePriority(battler2); + } + + return GetWhichBattlerFasterArgs(battler1, battler2, ignoreChosenMoves, ability1, ability2, + holdEffectBattler1, holdEffectBattler2, speedBattler1, speedBattler2, priority1, priority2); +} + static void SetActionsAndBattlersTurnOrder(void) { s32 turnOrderId = 0; @@ -4928,7 +4942,7 @@ static void SetActionsAndBattlersTurnOrder(void) && gActionsByTurnOrder[i] != B_ACTION_THROW_BALL && gActionsByTurnOrder[j] != B_ACTION_THROW_BALL) { - if (GetWhoStrikesFirst(battler1, battler2, FALSE)) + if (GetWhichBattlerFaster(battler1, battler2, FALSE)) SwapTurnOrder(i, j); } } @@ -5092,7 +5106,7 @@ static void TryChangeTurnOrder(void) if (gActionsByTurnOrder[i] == B_ACTION_USE_MOVE && gActionsByTurnOrder[j] == B_ACTION_USE_MOVE) { - if (GetWhoStrikesFirst(battler1, battler2, FALSE)) + if (GetWhichBattlerFaster(battler1, battler2, FALSE)) SwapTurnOrder(i, j); } } diff --git a/src/battle_util.c b/src/battle_util.c index 9e489e9e3e..fbd7b091ad 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -875,12 +875,12 @@ void HandleAction_ActionFinished(void) // have been executed before. The only recalculation needed is for moves/switch. Mega evolution is handled in src/battle_main.c/TryChangeOrder if((gActionsByTurnOrder[i] == B_ACTION_USE_MOVE && gActionsByTurnOrder[j] == B_ACTION_USE_MOVE)) { - if (GetWhoStrikesFirst(battler1, battler2, FALSE)) + if (GetWhichBattlerFaster(battler1, battler2, FALSE)) SwapTurnOrder(i, j); } else if ((gActionsByTurnOrder[i] == B_ACTION_SWITCH && gActionsByTurnOrder[j] == B_ACTION_SWITCH)) { - if (GetWhoStrikesFirst(battler1, battler2, TRUE)) // If the actions chosen are switching, we recalc order but ignoring the moves + if (GetWhichBattlerFaster(battler1, battler2, TRUE)) // If the actions chosen are switching, we recalc order but ignoring the moves SwapTurnOrder(i, j); } } @@ -2043,7 +2043,7 @@ u8 DoFieldEndTurnEffects(void) { if (!gProtectStructs[i].quash && !gProtectStructs[j].quash - && GetWhoStrikesFirst(gBattlerByTurnOrder[i], gBattlerByTurnOrder[j], FALSE)) + && GetWhichBattlerFaster(gBattlerByTurnOrder[i], gBattlerByTurnOrder[j], FALSE)) SwapTurnOrder(i, j); } } @@ -8259,13 +8259,13 @@ bool32 IsBattlerAlive(u32 battler) return TRUE; } -u32 GetBattleMonMoveSlot(struct BattlePokemon *battleMon, u32 move) +u32 GetMoveSlot(u16 *moves, u32 move) { u32 i; for (i = 0; i < MAX_MON_MOVES; i++) { - if (battleMon->moves[i] == move) + if (moves[i] == move) break; } return i; @@ -8555,8 +8555,8 @@ static inline u32 CalcMoveBasePower(u32 move, u32 battlerAtk, u32 battlerDef, u3 basePower *= 2; break; case EFFECT_TRUMP_CARD: - i = GetBattleMonMoveSlot(&gBattleMons[battlerAtk], move); - if (i != 4) + i = GetMoveSlot(gBattleMons[battlerAtk].moves, move); + if (i != MAX_MON_MOVES) { if (gBattleMons[battlerAtk].pp[i] >= ARRAY_COUNT(sTrumpCardPowerTable)) basePower = sTrumpCardPowerTable[ARRAY_COUNT(sTrumpCardPowerTable) - 1];