From b12f32667fa40042792ddb6754b00af471bc31fb Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Sat, 8 Mar 2025 03:53:14 -0500 Subject: [PATCH] Fix roll handling in AI damage calcs (#6396) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- include/battle.h | 5 +- include/battle_ai_util.h | 27 ++- include/battle_script_commands.h | 1 + include/battle_util.h | 4 + include/config/ai.h | 4 + src/battle_ai_main.c | 60 +++--- src/battle_ai_switch_items.c | 17 +- src/battle_ai_util.c | 311 ++++++++++++++++++------------- src/battle_debug.c | 2 +- src/battle_script_commands.c | 4 - 10 files changed, 246 insertions(+), 189 deletions(-) diff --git a/include/battle.h b/include/battle.h index c9517b9b71..8342677484 100644 --- a/include/battle.h +++ b/include/battle.h @@ -335,8 +335,9 @@ struct SwitchinCandidate struct SimulatedDamage { - s32 expected; - s32 minimum; + u16 minimum; + u16 median; + u16 maximum; }; // Ai Data used when deciding which move to use, computed only once before each turn's start. diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 4855184fac..6d1f5a9ed4 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -15,6 +15,12 @@ enum DamageRollType DMG_ROLL_HIGHEST, }; +enum DamageCalcContext +{ + AI_DEFENDING, + AI_ATTACKING, +}; + enum AIPivot { DONT_PIVOT, @@ -25,7 +31,7 @@ enum AIPivot bool32 AI_IsFaster(u32 battlerAi, u32 battlerDef, u32 move); bool32 AI_IsSlower(u32 battlerAi, u32 battlerDef, u32 move); bool32 AI_RandLessThan(u32 val); -u32 GetDmgRollType(u32 battlerAtk); +u32 AI_GetDamage(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum DamageCalcContext calcContext, struct AiLogicData *aiData); bool32 IsAiVsAiBattle(void); bool32 BattlerHasAi(u32 battlerId); bool32 IsAiBattlerAware(u32 battlerId); @@ -50,8 +56,8 @@ bool32 IsBattlerTrapped(u32 battler, bool32 switching); s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 moveConsidered); bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk); u32 NoOfHitsForTargetToFaintAI(u32 battlerDef, u32 battlerAtk); -u32 GetBestDmgMoveFromBattler(u32 battlerAtk, u32 battlerDef); -u32 GetBestDmgFromBattler(u32 battler, u32 battlerTarget); +u32 GetBestDmgMoveFromBattler(u32 battlerAtk, u32 battlerDef, enum DamageCalcContext calcContext); +u32 GetBestDmgFromBattler(u32 battler, u32 battlerTarget, enum DamageCalcContext calcContext); bool32 CanTargetMoveFaintAi(u32 move, u32 battlerDef, u32 battlerAtk, u32 nHits); bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dmgMod); s32 AI_DecideKnownAbilityForTurn(u32 battlerId); @@ -59,7 +65,8 @@ u32 AI_DecideHoldEffectForTurn(u32 battlerId); bool32 DoesBattlerIgnoreAbilityChecks(u32 battlerAtk, u32 atkAbility, u32 move); u32 AI_GetWeather(void); bool32 CanAIFaintTarget(u32 battlerAtk, u32 battlerDef, u32 numHits); -bool32 CanIndexMoveFaintTarget(u32 battlerAtk, u32 battlerDef, u32 index, u32 numHits); +bool32 CanIndexMoveFaintTarget(u32 battlerAtk, u32 battlerDef, u32 index, enum DamageCalcContext calcContext); +bool32 CanIndexMoveGuaranteeFaintTarget(u32 battlerAtk, u32 battlerDef, u32 index); bool32 HasDamagingMove(u32 battlerId); bool32 HasDamagingMoveOfType(u32 battlerId, u32 type); u32 GetBattlerSecondaryDamage(u32 battlerId); @@ -69,7 +76,7 @@ bool32 ShouldTryOHKO(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbil bool32 ShouldUseRecoilMove(u32 battlerAtk, u32 battlerDef, u32 recoilDmg, u32 moveIndex); u32 GetBattlerSideSpeedAverage(u32 battler); bool32 ShouldAbsorb(u32 battlerAtk, u32 battlerDef, u32 move, s32 damage); -bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent); +bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent, enum DamageCalcContext calcContext); bool32 ShouldSetScreen(u32 battlerAtk, u32 battlerDef, u32 moveEffect); enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 moveIndex); bool32 IsRecycleEncouragedItem(u32 item); @@ -101,13 +108,13 @@ bool32 ShouldLowerEvasion(u32 battlerAtk, u32 battlerDef, u32 defAbility); bool32 IsAffectedByPowder(u32 battler, u32 ability, u32 holdEffect); bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, u32 category); s32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo); -struct SimulatedDamage AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, bool32 considerZPower, enum DamageRollType rollType); -struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, bool32 considerZPower, u32 weather, enum DamageRollType rollType); +struct SimulatedDamage AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, bool32 considerZPower); +struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, bool32 considerZPower, u32 weather); bool32 AI_IsDamagedByRecoil(u32 battler); u32 GetNoOfHitsToKO(u32 dmg, s32 hp); u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef); -u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex); -u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef); +u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum DamageCalcContext calcContext); +u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef, enum DamageCalcContext calcContext); uq4_12_t AI_GetMoveEffectiveness(u32 move, u32 battlerAtk, u32 battlerDef); u16 *GetMovesArray(u32 battler); bool32 IsConfusionMoveEffect(u32 moveEffect); @@ -210,7 +217,7 @@ void IncreaseSleepScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); void IncreaseConfusionScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); void IncreaseFrostbiteScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); -s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool32 isPartyMonAttacker, enum DamageRollType rollType); +s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool32 isPartyMonAttacker); u32 AI_WhoStrikesFirstPartyMon(u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 moveConsidered); s32 AI_TryToClearStats(u32 battlerAtk, u32 battlerDef, bool32 isDoubleBattle); bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef); diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index cc6c09012c..1b72239d00 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -23,6 +23,7 @@ struct PickupItem }; s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, u32 abilityAtk, u32 abilityDef, u32 holdEffectAtk); +s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, u32 abilityAtk, u32 abilityDef, u32 holdEffectAtk); s32 GetCritHitOdds(s32 critChanceIndex); u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect); u8 GetBattlerTurnOrderNum(u8 battlerId); diff --git a/include/battle_util.h b/include/battle_util.h index 21ce76de40..37588d246d 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -96,6 +96,10 @@ enum ItemEffect #define DMG_ROLL_PERCENT_LO 85 #define DMG_ROLL_PERCENT_HI 100 +// Crit chance exceptions +#define CRITICAL_HIT_BLOCKED -1 +#define CRITICAL_HIT_ALWAYS -2 + // for Natural Gift and Fling struct TypePower { diff --git a/include/config/ai.h b/include/config/ai.h index 1fe0c822d2..b44fc46ba9 100644 --- a/include/config/ai.h +++ b/include/config/ai.h @@ -47,6 +47,10 @@ // AI held item-based move scoring #define LOW_ACCURACY_THRESHOLD 75 // Moves with accuracy equal OR below this value are considered low accuracy +// AI damage calc considerations +#define RISKY_AI_CRIT_STAGE_THRESHOLD 2 // Stat stages at which Risky will assume it gets a crit +#define RISKY_AI_CRIT_THRESHOLD_GEN_1 128 // "Stat stage" at which Risky will assume it gets a crit with gen 1 mechanics (this translates to an X / 255 % crit threshold) + // AI prediction chances #define PREDICT_SWITCH_CHANCE 50 diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index e04ca63e68..b95601f37d 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -413,7 +413,6 @@ static u32 Ai_SetMoveAccuracy(struct AiLogicData *aiData, u32 battlerAtk, u32 ba static void CalcBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u32 battlerDef, u32 weather) { u32 moveIndex, move; - u32 rollType = GetDmgRollType(battlerAtk); u16 *moves = GetMovesArray(battlerAtk); for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) @@ -427,7 +426,7 @@ static void CalcBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u //&& !IsBattleMoveStatus(move) /* we want to get effectiveness and accuracy of status moves */ && !(aiData->moveLimitations[battlerAtk] & (1u << moveIndex))) { - dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE, weather, rollType); + dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE, weather); aiData->moveAccuracy[battlerAtk][battlerDef][moveIndex] = Ai_SetMoveAccuracy(aiData, battlerAtk, battlerDef, move); } aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex] = dmg; @@ -2734,8 +2733,10 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (IsBattleMoveStatus(move)) return score; // status moves aren't accounted here - if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, 0) && GetMoveEffect(move) != EFFECT_EXPLOSION) + if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, AI_ATTACKING) && GetMoveEffect(move) != EFFECT_EXPLOSION) { + if (CanIndexMoveGuaranteeFaintTarget(battlerAtk, battlerDef, movesetIndex)) + ADJUST_SCORE(1); // Bonus point if the KO is guaranteed if (AI_IsFaster(battlerAtk, battlerDef, move)) ADJUST_SCORE(FAST_KILL); else @@ -2895,7 +2896,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (MoveAlwaysCrits(move) && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK) && AI_IsFaster(battlerAtk, battlerAtkPartner, move) - && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 1)) + && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, AI_ATTACKING)) { RETURN_SCORE_PLUS(GOOD_EFFECT); } @@ -2939,7 +2940,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; case ABILITY_WATER_COMPACTION: - if (moveType == TYPE_WATER && GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) >= 4) + if (moveType == TYPE_WATER && GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, AI_ATTACKING) >= 4) { RETURN_SCORE_PLUS(WEAK_EFFECT); // only mon with this ability is weak to water so only make it okay if we do very little damage } @@ -2966,7 +2967,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) && !IsBattleMoveStatus(move) && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL) && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK) - && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 1)) + && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, AI_ATTACKING)) { RETURN_SCORE_PLUS(WEAK_EFFECT); } @@ -2975,7 +2976,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (!IsBattleMoveStatus(move) && (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG) && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPEED) - && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 1)) + && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, AI_ATTACKING)) { RETURN_SCORE_PLUS(WEAK_EFFECT); } @@ -3032,7 +3033,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) && !IsBattleMoveStatus(move) && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL) && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK) - && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 0)) + && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, AI_ATTACKING)) { RETURN_SCORE_PLUS(WEAK_EFFECT); } @@ -3180,7 +3181,7 @@ static s32 CompareMoveAccuracies(u32 battlerAtk, u32 battlerDef, u32 moveSlot1, static inline bool32 ShouldUseSpreadDamageMove(u32 battlerAtk, u32 move, u32 moveIndex, u32 hitsToFaintOpposingBattler) { u32 partnerBattler = BATTLE_PARTNER(battlerAtk); - u32 noOfHitsToFaintPartner = GetNoOfHitsToKOBattler(battlerAtk, partnerBattler, moveIndex); + u32 noOfHitsToFaintPartner = GetNoOfHitsToKOBattler(battlerAtk, partnerBattler, moveIndex, AI_ATTACKING); return (IsDoubleBattle() && noOfHitsToFaintPartner != 0 // Immunity check && IsBattlerAlive(partnerBattler) @@ -3205,7 +3206,7 @@ static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId) { if (moves[i] != MOVE_NONE && GetMovePower(moves[i]) != 0) { - noOfHits[i] = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, i); + noOfHits[i] = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, i, AI_ATTACKING); if (ShouldUseSpreadDamageMove(battlerAtk,moves[i], i, noOfHits[i])) { noOfHits[i] = -1; @@ -3225,9 +3226,6 @@ static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId) viableMoveScores[i] = 0; isTwoTurnNotSemiInvulnerableMove[i] = FALSE; } - /* - Test_MgbaPrintf("%S: required hits: %d Dmg: %d", gMoveNames[moves[i]], noOfHits[i], AI_DATA->simulatedDmg[battlerAtk][battlerDef][i]); - */ } // Priority list: @@ -3581,7 +3579,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) break; } - if (ShouldRecover(battlerAtk, battlerDef, move, healPercent)) + if (ShouldRecover(battlerAtk, battlerDef, move, healPercent, AI_DEFENDING)) ADJUST_SCORE(DECENT_EFFECT); } break; @@ -3591,7 +3589,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_MORNING_SUN: case EFFECT_SYNTHESIS: case EFFECT_MOONLIGHT: - if (ShouldRecover(battlerAtk, battlerDef, move, 50)) + if (ShouldRecover(battlerAtk, battlerDef, move, 50, AI_DEFENDING)) ADJUST_SCORE(GOOD_EFFECT); break; case EFFECT_TOXIC: @@ -3613,7 +3611,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) { break; } - else if (ShouldRecover(battlerAtk, battlerDef, move, 100)) + else if (ShouldRecover(battlerAtk, battlerDef, move, 100, AI_DEFENDING)) { if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_CURE_SLP || aiData->holdEffects[battlerAtk] == HOLD_EFFECT_CURE_STATUS @@ -3933,7 +3931,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_FELL_STINGER: if (gBattleMons[battlerAtk].statStages[STAT_ATK] < MAX_STAT_STAGE && aiData->abilities[battlerAtk] != ABILITY_CONTRARY - && CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, 0)) + && CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, AI_ATTACKING)) ADJUST_SCORE(BEST_EFFECT); break; case EFFECT_BELLY_DRUM: @@ -3965,7 +3963,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_FIRST_TURN_ONLY: if (ShouldFakeOut(battlerAtk, battlerDef, move) && MoveHasAdditionalEffectWithChance(move, MOVE_EFFECT_FLINCH, 100)) ADJUST_SCORE(GOOD_EFFECT); - else if (gDisableStructs[battlerAtk].isFirstTurn && GetBestDmgMoveFromBattler(battlerAtk, battlerDef) == move) + else if (gDisableStructs[battlerAtk].isFirstTurn && GetBestDmgMoveFromBattler(battlerAtk, battlerDef, AI_ATTACKING) == move) ADJUST_SCORE(BEST_EFFECT); break; case EFFECT_STOCKPILE: @@ -4538,20 +4536,20 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) break; case EFFECT_COUNTER: if ((!IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) && predictedMove != MOVE_NONE) - && (GetNoOfHitsToKOBattler(battlerDef, battlerAtk, predictedMoveSlot) >= 2) + && (GetNoOfHitsToKOBattler(battlerDef, battlerAtk, predictedMoveSlot, AI_DEFENDING) >= 2) && (GetBattleMoveCategory(predictedMove) == DAMAGE_CATEGORY_PHYSICAL)) ADJUST_SCORE(GOOD_EFFECT); break; case EFFECT_MIRROR_COAT: if ((!IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) && predictedMove != MOVE_NONE) - && (GetNoOfHitsToKOBattler(battlerDef, battlerAtk, predictedMoveSlot) >= 2) + && (GetNoOfHitsToKOBattler(battlerDef, battlerAtk, predictedMoveSlot, AI_DEFENDING) >= 2) && (GetBattleMoveCategory(predictedMove) == DAMAGE_CATEGORY_SPECIAL)) ADJUST_SCORE(GOOD_EFFECT); break; case EFFECT_SHORE_UP: - if ((AI_GetWeather() & B_WEATHER_SANDSTORM) && ShouldRecover(battlerAtk, battlerDef, move, 67)) + if ((AI_GetWeather() & B_WEATHER_SANDSTORM) && ShouldRecover(battlerAtk, battlerDef, move, 67, AI_DEFENDING)) ADJUST_SCORE(DECENT_EFFECT); - else if (ShouldRecover(battlerAtk, battlerDef, move, 50)) + else if (ShouldRecover(battlerAtk, battlerDef, move, 50, AI_DEFENDING)) ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_ENDEAVOR: @@ -4577,8 +4575,8 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) //case EFFECT_SKY_DROP //break; case EFFECT_JUNGLE_HEALING: - if (ShouldRecover(battlerAtk, battlerDef, move, 25) - || ShouldRecover(BATTLE_PARTNER(battlerAtk), battlerDef, move, 25) + if (ShouldRecover(battlerAtk, battlerDef, move, 25, AI_DEFENDING) + || ShouldRecover(BATTLE_PARTNER(battlerAtk), battlerDef, move, 25, AI_DEFENDING) || gBattleMons[battlerAtk].status1 & STATUS1_ANY || gBattleMons[BATTLE_PARTNER(battlerAtk)].status1 & STATUS1_ANY) ADJUST_SCORE(GOOD_EFFECT); @@ -4816,7 +4814,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(GOOD_EFFECT); break; case MOVE_EFFECT_THROAT_CHOP: - if (IsSoundMove(GetBestDmgMoveFromBattler(battlerDef, battlerAtk))) + if (IsSoundMove(GetBestDmgMoveFromBattler(battlerDef, battlerAtk, AI_DEFENDING))) { if (AI_IsFaster(battlerAtk, battlerDef, move)) ADJUST_SCORE(GOOD_EFFECT); @@ -4849,12 +4847,12 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score if (GetMovePower(move) != 0) { - if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == 0) + if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, AI_ATTACKING) == 0) ADJUST_AND_RETURN_SCORE(NO_DAMAGE_OR_FAILS); // No point in checking the move further so return early else { if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & (AI_FLAG_RISKY | AI_FLAG_PREFER_HIGHEST_DAMAGE_MOVE) - && GetBestDmgMoveFromBattler(battlerAtk, battlerDef) == move) + && GetBestDmgMoveFromBattler(battlerAtk, battlerDef, AI_ATTACKING) == move) ADJUST_SCORE(BEST_DAMAGE_MOVE); else ADJUST_SCORE(AI_CompareDamagingMoves(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex)); @@ -5078,9 +5076,9 @@ static s32 AI_TryTo2HKO(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) return score; - if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == 1) + if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, AI_ATTACKING) == 1) ADJUST_SCORE(BEST_EFFECT); - else if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == 2) + else if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, AI_ATTACKING) == 2) ADJUST_SCORE(DECENT_EFFECT); return score; @@ -5256,7 +5254,7 @@ static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } // consider target HP - if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) + if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, AI_ATTACKING)) { ADJUST_SCORE(DECENT_EFFECT); } @@ -5437,7 +5435,7 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { case EFFECT_PURSUIT: { - u32 hitsToKO = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex); + u32 hitsToKO = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, AI_ATTACKING); if (hitsToKO == 2) ADJUST_SCORE(GOOD_EFFECT); else if (hitsToKO == 1) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index c468bf6210..466f6c5cdf 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -220,7 +220,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) hasSuperEffectiveMove = TRUE; // Get maximum damage mon can deal - damageDealt = AI_DATA->simulatedDmg[battler][opposingBattler][i].expected; + damageDealt = AI_GetDamage(battler, opposingBattler, i, AI_ATTACKING, AI_DATA); if(damageDealt > maxDamageDealt) { maxDamageDealt = damageDealt; @@ -248,7 +248,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) playerMove = gBattleMons[opposingBattler].moves[i]; if (playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH) { - damageTaken = AI_CalcDamage(playerMove, opposingBattler, battler, &effectiveness, FALSE, weather, DMG_ROLL_HIGHEST).expected; + damageTaken = AI_CalcDamage(playerMove, opposingBattler, battler, &effectiveness, FALSE, weather).median; if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move { return maxDamageTaken = damageTaken; @@ -454,7 +454,7 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) // Only check damage if it's a damaging move if (!IsBattleMoveStatus(aiMove)) { - if (AI_DATA->simulatedDmg[battler][opposingBattler][i].expected > gBattleMons[opposingBattler].hp) + if (AI_GetDamage(battler, opposingBattler, i, AI_ATTACKING, AI_DATA) > gBattleMons[opposingBattler].hp) return FALSE; } } @@ -1355,7 +1355,6 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva int i, j; int dmg, bestDmg = 0; int bestMonId = PARTY_SIZE; - u32 rollType = GetDmgRollType(battler); u32 aiMove; @@ -1371,7 +1370,7 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva if (aiMove != MOVE_NONE && !IsBattleMoveStatus(aiMove)) { aiMove = GetMonData(&party[i], MON_DATA_MOVE1 + j); - dmg = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE, rollType); + dmg = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE); if (bestDmg < dmg) { bestDmg = dmg; @@ -1826,7 +1825,7 @@ static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattle playerMove = gBattleMons[opposingBattler].moves[i]; if (playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH) { - damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, FALSE, DMG_ROLL_HIGHEST); + damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, FALSE); if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move return damageTaken; if (damageTaken > maxDamageTaken) @@ -1959,9 +1958,9 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, if (aiMove != MOVE_NONE && !IsBattleMoveStatus(aiMove)) { if (AI_THINKING_STRUCT->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_CONSERVATIVE) - damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE, DMG_ROLL_LOWEST); + damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE); else - damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE, DMG_ROLL_DEFAULT); + damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE); } // Offensive switchin decisions are based on which whether switchin moves first and whether it can win a 1v1 @@ -2208,7 +2207,7 @@ static bool32 AiExpectsToFaintPlayer(u32 battler) return FALSE; // AI not planning to use move if (GetBattlerSide(target) != GetBattlerSide(battler) - && CanIndexMoveFaintTarget(battler, target, gBattleStruct->aiMoveOrAction[battler], 0) + && CanIndexMoveFaintTarget(battler, target, gBattleStruct->aiMoveOrAction[battler], AI_ATTACKING) && AI_IsFaster(battler, target, GetAIChosenMove(battler))) { // We expect to faint the target and move first -> dont use an item diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 2bafe83d22..77cd3cc4ac 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -24,14 +24,29 @@ #include "constants/items.h" // Functions -u32 GetDmgRollType(u32 battlerAtk) +u32 AI_GetDamage(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum DamageCalcContext calcContext, struct AiLogicData *aiData) { - if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_RISKY) - return DMG_ROLL_HIGHEST; - if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) - return DMG_ROLL_LOWEST; + if (calcContext == AI_ATTACKING && BattlerHasAi(battlerAtk)) + { - return DMG_ROLL_DEFAULT; + if ((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_RISKY) && !(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE)) // Risky assumes it deals max damage + return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum; + if ((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) && !(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_RISKY)) // Conservative assumes it deals min damage + return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum; + return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].median; // Default assumes it deals median damage + } + else if (calcContext == AI_DEFENDING && BattlerHasAi(battlerDef)) + { + if ((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_RISKY) && !(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE)) // Risky assumes it takes min damage + return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum; + if ((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) && !(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_RISKY)) // Conservative assumes it takes max damage + return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum; + return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].median; // Default assumes it takes median damage + } + else + { + return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].median; + } } bool32 AI_IsFaster(u32 battlerAi, u32 battlerDef, u32 move) @@ -385,7 +400,7 @@ bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, u32 category) } // To save computation time this function has 2 variants. One saves, sets and restores battlers, while the other doesn't. -struct SimulatedDamage AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, bool32 considerZPower, enum DamageRollType rollType) +struct SimulatedDamage AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, bool32 considerZPower) { struct SimulatedDamage dmg; @@ -393,7 +408,7 @@ struct SimulatedDamage AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 b SaveBattlerData(battlerDef); SetBattlerData(battlerAtk); SetBattlerData(battlerDef); - dmg = AI_CalcDamage(move, battlerAtk, battlerDef, typeEffectiveness, considerZPower, AI_GetWeather(), rollType); + dmg = AI_CalcDamage(move, battlerAtk, battlerDef, typeEffectiveness, considerZPower, AI_GetWeather()); RestoreBattlerData(battlerAtk); RestoreBattlerData(battlerDef); return dmg; @@ -538,58 +553,63 @@ static inline void AI_RestoreBattlerTypes(u32 battlerAtk, u32 *types) gBattleMons[battlerAtk].types[2] = types[2]; } -static inline void CalcDynamicMoveDamage(struct DamageCalculationData *damageCalcData, s32 *expectedDamage, s32 *minimumDamage, u32 holdEffectAtk, u32 abilityAtk) +static inline void CalcDynamicMoveDamage(struct DamageCalculationData *damageCalcData, u16 *medianDamage, u16 *minimumDamage, u16 *maximumDamage, u32 holdEffectAtk, u32 abilityAtk) { u32 move = damageCalcData->move; u32 effect = GetMoveEffect(move); - s32 expected = *expectedDamage; - s32 minimum = *minimumDamage; + u16 median = *medianDamage; + u16 minimum = *minimumDamage; + u16 maximum = *maximumDamage; switch (effect) { case EFFECT_LEVEL_DAMAGE: - expected = minimum = gBattleMons[damageCalcData->battlerAtk].level * (abilityAtk == ABILITY_PARENTAL_BOND ? 2 : 1); + median = maximum = minimum = gBattleMons[damageCalcData->battlerAtk].level * (abilityAtk == ABILITY_PARENTAL_BOND ? 2 : 1); break; case EFFECT_PSYWAVE: - expected = minimum = gBattleMons[damageCalcData->battlerAtk].level * (abilityAtk == ABILITY_PARENTAL_BOND ? 2 : 1); + median = maximum = minimum = gBattleMons[damageCalcData->battlerAtk].level * (abilityAtk == ABILITY_PARENTAL_BOND ? 2 : 1); break; case EFFECT_FIXED_DAMAGE_ARG: - expected = minimum = GetMoveFixedDamage(move) * (abilityAtk == ABILITY_PARENTAL_BOND ? 2 : 1); + median = maximum = minimum = GetMoveFixedDamage(move) * (abilityAtk == ABILITY_PARENTAL_BOND ? 2 : 1); break; case EFFECT_MULTI_HIT: if (move == MOVE_WATER_SHURIKEN && gBattleMons[damageCalcData->battlerAtk].species == SPECIES_GRENINJA_ASH) { - expected *= 3; + median *= 3; minimum *= 3; + maximum *= 3; } else if (abilityAtk == ABILITY_SKILL_LINK) { - expected *= 5; + median *= 5; minimum *= 5; + maximum *= 5; } else if (holdEffectAtk == HOLD_EFFECT_LOADED_DICE) { - expected *= 9; - expected /= 2; + median *= 9; + median /= 2; minimum *= 4; + maximum *= 5; } else { - expected *= 3; + median *= 3; minimum *= 2; + maximum *= 5; } break; case EFFECT_ENDEAVOR: // If target has less HP than user, Endeavor does no damage - expected = minimum = max(0, gBattleMons[damageCalcData->battlerDef].hp - gBattleMons[damageCalcData->battlerAtk].hp); + median = maximum = minimum = max(0, gBattleMons[damageCalcData->battlerDef].hp - gBattleMons[damageCalcData->battlerAtk].hp); break; case EFFECT_SUPER_FANG: - expected = minimum = (abilityAtk == ABILITY_PARENTAL_BOND + median = maximum = minimum = (abilityAtk == ABILITY_PARENTAL_BOND ? max(2, gBattleMons[damageCalcData->battlerDef].hp * 3 / 4) : max(1, gBattleMons[damageCalcData->battlerDef].hp / 2)); break; case EFFECT_FINAL_GAMBIT: - expected = minimum = gBattleMons[damageCalcData->battlerAtk].hp; + median = maximum = minimum = gBattleMons[damageCalcData->battlerAtk].hp; break; case EFFECT_BEAT_UP: if (B_BEAT_UP >= GEN_5) @@ -598,10 +618,10 @@ static inline void CalcDynamicMoveDamage(struct DamageCalculationData *damageCal u32 i; gBattleStruct->beatUpSlot = 0; damageCalcData->isCrit = FALSE; - expected = 0; + median = 0; for (i = 0; i < partyCount; i++) - expected += CalculateMoveDamage(damageCalcData, 0); - minimum = expected; + median += CalculateMoveDamage(damageCalcData, 0); + maximum = minimum = median; gBattleStruct->beatUpSlot = 0; } break; @@ -611,20 +631,39 @@ static inline void CalcDynamicMoveDamage(struct DamageCalculationData *damageCal u32 strikeCount = GetMoveStrikeCount(move); if (strikeCount > 1 && effect != EFFECT_TRIPLE_KICK) { - expected *= strikeCount; + median *= strikeCount; minimum *= strikeCount; + maximum *= strikeCount; } - if (expected == 0) - expected = 1; + if (median == 0) + median = 1; if (minimum == 0) minimum = 1; + if (maximum == 0) + maximum = 1; - *expectedDamage = expected; + *medianDamage = median; *minimumDamage = minimum; + *maximumDamage = maximum; } -struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, bool32 considerZPower, u32 weather, enum DamageRollType rollType) +static inline bool32 ShouldCalcCritDamage(s32 critChanceIndex, u32 battlerAtk) +{ + if (critChanceIndex == CRITICAL_HIT_ALWAYS) + return TRUE; + if (critChanceIndex >= RISKY_AI_CRIT_STAGE_THRESHOLD // Not guaranteed but above Risky threshold + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_RISKY) + && GetGenConfig(GEN_CONFIG_CRIT_CHANCE) != GEN_1) + return TRUE; + if (critChanceIndex >= RISKY_AI_CRIT_THRESHOLD_GEN_1 // Not guaranteed but above Risky threshold + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_RISKY) + && GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_1) + return TRUE; + return FALSE; +} + +struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, bool32 considerZPower, u32 weather) { struct SimulatedDamage simDamage; s32 moveType; @@ -678,57 +717,40 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u damageCalcData.randomFactor = FALSE; damageCalcData.updateFlags = FALSE; - critChanceIndex = CalcCritChanceStage(battlerAtk, battlerDef, move, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk]); - if (critChanceIndex > 1) // Consider crit damage only if a move has at least +2 crit chance + // Get crit chance + if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_1) + critChanceIndex = CalcCritChanceStageGen1(battlerAtk, battlerDef, move, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk]); + else + critChanceIndex = CalcCritChanceStage(battlerAtk, battlerDef, move, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk]); + // Use crit damage + if (ShouldCalcCritDamage(critChanceIndex, battlerAtk)) { - damageCalcData.isCrit = FALSE; - s32 nonCritDmg = CalculateMoveDamageVars(&damageCalcData, fixedBasePower, - effectivenessMultiplier, weather, - aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], - aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); - - nonCritDmg = ApplyModifiersAfterDmgRoll(nonCritDmg, &damageCalcData, effectivenessMultiplier, - aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], - aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); - damageCalcData.isCrit = TRUE; s32 critDmg = CalculateMoveDamageVars(&damageCalcData, fixedBasePower, effectivenessMultiplier, weather, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); - simDamage.expected = GetDamageByRollType(critDmg, rollType); - simDamage.expected = ApplyModifiersAfterDmgRoll(simDamage.expected, &damageCalcData, effectivenessMultiplier, + simDamage.minimum = GetDamageByRollType(critDmg, DMG_ROLL_LOWEST); + simDamage.minimum = ApplyModifiersAfterDmgRoll(simDamage.minimum, &damageCalcData, effectivenessMultiplier, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); - simDamage.minimum = (GetCritHitOdds(critChanceIndex) == 1) ? LowestRollDmg(critDmg) : LowestRollDmg(nonCritDmg); - simDamage.minimum = ApplyModifiersAfterDmgRoll(simDamage.minimum, &damageCalcData, effectivenessMultiplier, - aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], - aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); + simDamage.median = GetDamageByRollType(critDmg, DMG_ROLL_DEFAULT); + simDamage.median = ApplyModifiersAfterDmgRoll(simDamage.median, &damageCalcData, effectivenessMultiplier, + aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], + aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); + + simDamage.maximum = GetDamageByRollType(critDmg, DMG_ROLL_HIGHEST); + simDamage.maximum = ApplyModifiersAfterDmgRoll(simDamage.maximum, &damageCalcData, effectivenessMultiplier, + aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], + aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); } - else if (critChanceIndex == -2) // Guaranteed critical - { - damageCalcData.isCrit = TRUE; - s32 critDmg = CalculateMoveDamageVars(&damageCalcData, fixedBasePower, - effectivenessMultiplier, weather, - aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], - aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); - simDamage.expected = GetDamageByRollType(critDmg, rollType); - simDamage.minimum = LowestRollDmg(critDmg); - - simDamage.expected = ApplyModifiersAfterDmgRoll(simDamage.expected, &damageCalcData, effectivenessMultiplier, - aiData->abilities[battlerAtk], aiData->abilities[battlerDef], - aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef]); - - simDamage.minimum = ApplyModifiersAfterDmgRoll(simDamage.minimum, &damageCalcData, effectivenessMultiplier, - aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], - aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); - } + // Use non-crit damage else { - s32 nonCritDmg = 0; + s32 nonCritDmg = 0, tripleKickDmgMin = 0, tripleKickDmgDefault = 0, tripleKickDmgMax = 0; if (moveEffect == EFFECT_TRIPLE_KICK) { for (gMultiHitCounter = GetMoveStrikeCount(move); gMultiHitCounter > 0; gMultiHitCounter--) // The global is used to simulate actual damage done @@ -738,17 +760,24 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); - simDamage.expected = GetDamageByRollType(oneTripleKickHit, rollType); - simDamage.minimum = LowestRollDmg(oneTripleKickHit); - - nonCritDmg += ApplyModifiersAfterDmgRoll(simDamage.expected, &damageCalcData, effectivenessMultiplier, + simDamage.minimum = GetDamageByRollType(oneTripleKickHit, DMG_ROLL_LOWEST); + tripleKickDmgMin += ApplyModifiersAfterDmgRoll(simDamage.minimum, &damageCalcData, effectivenessMultiplier, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); - nonCritDmg += ApplyModifiersAfterDmgRoll(simDamage.minimum, &damageCalcData, effectivenessMultiplier, + simDamage.median = GetDamageByRollType(oneTripleKickHit, DMG_ROLL_DEFAULT); + tripleKickDmgDefault += ApplyModifiersAfterDmgRoll(simDamage.median, &damageCalcData, effectivenessMultiplier, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); + + simDamage.maximum = GetDamageByRollType(oneTripleKickHit, DMG_ROLL_HIGHEST); + tripleKickDmgMax += ApplyModifiersAfterDmgRoll(simDamage.maximum, &damageCalcData, effectivenessMultiplier, + aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], + aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); } + simDamage.minimum = tripleKickDmgMin; + simDamage.median = tripleKickDmgDefault; + simDamage.maximum = tripleKickDmgMax; } else { @@ -757,24 +786,29 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); - simDamage.expected = GetDamageByRollType(nonCritDmg, rollType); - simDamage.minimum = LowestRollDmg(nonCritDmg); + simDamage.minimum = GetDamageByRollType(nonCritDmg, DMG_ROLL_LOWEST); + simDamage.minimum = ApplyModifiersAfterDmgRoll(simDamage.minimum, &damageCalcData, effectivenessMultiplier, + aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], + aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); - simDamage.expected = ApplyModifiersAfterDmgRoll(simDamage.expected, &damageCalcData, effectivenessMultiplier, + simDamage.median = GetDamageByRollType(nonCritDmg, DMG_ROLL_DEFAULT); + simDamage.median = ApplyModifiersAfterDmgRoll(simDamage.median, &damageCalcData, effectivenessMultiplier, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); - simDamage.minimum = ApplyModifiersAfterDmgRoll(simDamage.minimum, &damageCalcData, effectivenessMultiplier, - aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], - aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); + simDamage.maximum = GetDamageByRollType(nonCritDmg, DMG_ROLL_HIGHEST); + simDamage.maximum = ApplyModifiersAfterDmgRoll(simDamage.maximum, &damageCalcData, effectivenessMultiplier, + aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], + aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); } } if (GetActiveGimmick(battlerAtk) != GIMMICK_Z_MOVE) { CalcDynamicMoveDamage(&damageCalcData, - &simDamage.expected, + &simDamage.median, &simDamage.minimum, + &simDamage.maximum, aiData->holdEffects[battlerAtk], aiData->abilities[battlerAtk]); } @@ -783,8 +817,9 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u } else { - simDamage.expected = 0; simDamage.minimum = 0; + simDamage.median = 0; + simDamage.maximum = 0; } // convert multiper to AI_EFFECTIVENESS_xX @@ -1065,14 +1100,14 @@ u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef) return GetNoOfHitsToKO(dmg, gBattleMons[battlerDef].hp); } -u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex) +u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum DamageCalcContext calcContext) { - return GetNoOfHitsToKOBattlerDmg(AI_DATA->simulatedDmg[battlerAtk][battlerDef][moveIndex].expected, battlerDef); + return GetNoOfHitsToKOBattlerDmg(AI_GetDamage(battlerAtk, battlerDef, moveIndex, calcContext, AI_DATA), battlerDef); } -u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef) +u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef, enum DamageCalcContext calcContext) { - int bestDmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex].expected; + int bestDmg = AI_GetDamage(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, calcContext, AI_DATA); return (bestDmg * 100) / gBattleMons[battlerDef].maxHP; } @@ -1182,15 +1217,15 @@ static bool32 CanEndureHit(u32 battler, u32 battlerTarget, u32 move) // Check if target has means to faint ai mon. bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk) { - s32 i; + s32 moveIndex; u32 unusable = AI_DATA->moveLimitations[battlerDef]; u16 *moves = GetMovesArray(battlerDef); - for (i = 0; i < MAX_MON_MOVES; i++) + for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) { - if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & (1u << i)) - && AI_DATA->simulatedDmg[battlerDef][battlerAtk][i].expected >= gBattleMons[battlerAtk].hp - && !CanEndureHit(battlerDef, battlerAtk, moves[i])) + if (moves[moveIndex] != MOVE_NONE && moves[moveIndex] != MOVE_UNAVAILABLE && !(unusable & (1u << moveIndex)) + && AI_GetDamage(battlerDef, battlerAtk, moveIndex, AI_DEFENDING, AI_DATA) >= gBattleMons[battlerAtk].hp + && !CanEndureHit(battlerDef, battlerAtk, moves[moveIndex])) { return TRUE; } @@ -1207,7 +1242,7 @@ u32 NoOfHitsForTargetToFaintAI(u32 battlerDef, u32 battlerAtk) for (i = 0; i < MAX_MON_MOVES; i++) { - currNumberOfHits = GetNoOfHitsToKOBattler(battlerDef, battlerAtk, i); + currNumberOfHits = GetNoOfHitsToKOBattler(battlerDef, battlerAtk, i, AI_DEFENDING); if (currNumberOfHits != 0) { if (currNumberOfHits < leastNumberOfHits) @@ -1217,41 +1252,41 @@ u32 NoOfHitsForTargetToFaintAI(u32 battlerDef, u32 battlerAtk) return leastNumberOfHits; } -u32 GetBestDmgMoveFromBattler(u32 battlerAtk, u32 battlerDef) +u32 GetBestDmgMoveFromBattler(u32 battlerAtk, u32 battlerDef, enum DamageCalcContext calcContext) { - u32 i; + u32 moveIndex; u32 move = 0; u32 bestDmg = 0; u32 unusable = AI_DATA->moveLimitations[battlerAtk]; u16 *moves = GetMovesArray(battlerAtk); - for (i = 0; i < MAX_MON_MOVES; i++) + for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) { - if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & (1u << i)) - && bestDmg < AI_DATA->simulatedDmg[battlerAtk][battlerDef][i].expected) + if (moves[moveIndex] != MOVE_NONE && moves[moveIndex] != MOVE_UNAVAILABLE && !(unusable & (1u << moveIndex)) + && bestDmg < AI_GetDamage(battlerAtk, battlerDef, moveIndex, calcContext, AI_DATA)) { - bestDmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i].expected; - move = moves[i]; + bestDmg = AI_GetDamage(battlerAtk, battlerDef, moveIndex, calcContext, AI_DATA); + move = moves[moveIndex]; } } return move; } -u32 GetBestDmgFromBattler(u32 battler, u32 battlerTarget) +u32 GetBestDmgFromBattler(u32 battler, u32 battlerTarget, enum DamageCalcContext calcContext) { - u32 i; + u32 moveIndex; u32 bestDmg = 0; u32 unusable = AI_DATA->moveLimitations[battler]; u16 *moves = GetMovesArray(battler); - for (i = 0; i < MAX_MON_MOVES; i++) + for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) { - if (moves[i] != MOVE_NONE - && moves[i] != MOVE_UNAVAILABLE - && !(unusable & (1u << i)) - && bestDmg < AI_DATA->simulatedDmg[battler][battlerTarget][i].expected) + if (moves[moveIndex] != MOVE_NONE + && moves[moveIndex] != MOVE_UNAVAILABLE + && !(unusable & (1u << moveIndex)) + && bestDmg < AI_GetDamage(battler, battlerTarget, moveIndex, calcContext, AI_DATA)) { - bestDmg = AI_DATA->simulatedDmg[battler][battlerTarget][i].expected; + bestDmg = AI_GetDamage(battler, battlerTarget, moveIndex, calcContext, AI_DATA); } } @@ -1262,16 +1297,16 @@ u32 GetBestDmgFromBattler(u32 battler, u32 battlerTarget) // If numHits > 1, check if the target will be KO'ed by that number of hits (ignoring healing effects) bool32 CanAIFaintTarget(u32 battlerAtk, u32 battlerDef, u32 numHits) { - s32 i, dmg; + s32 moveIndex, dmg; u32 moveLimitations = AI_DATA->moveLimitations[battlerAtk]; u16 *moves = gBattleMons[battlerAtk].moves; - for (i = 0; i < MAX_MON_MOVES; i++) + for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) { - if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(moveLimitations & (1u << i))) + if (moves[moveIndex] != MOVE_NONE && moves[moveIndex] != MOVE_UNAVAILABLE && !(moveLimitations & (1u << moveIndex))) { // Use the pre-calculated value in simulatedDmg instead of re-calculating it - dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i].expected; + dmg = AI_GetDamage(battlerAtk, battlerDef, moveIndex, AI_ATTACKING, AI_DATA); if (numHits) dmg *= numHits; @@ -1281,7 +1316,7 @@ bool32 CanAIFaintTarget(u32 battlerAtk, u32 battlerDef, u32 numHits) if (numHits > 1) return TRUE; - if (!CanEndureHit(battlerAtk, battlerDef, moves[i])) + if (!CanEndureHit(battlerAtk, battlerDef, moves[moveIndex])) return TRUE; } } @@ -1295,7 +1330,7 @@ bool32 CanTargetMoveFaintAi(u32 move, u32 battlerDef, u32 battlerAtk, u32 nHits) u32 indexSlot = GetMoveSlot(GetMovesArray(battlerDef), move); if (indexSlot < MAX_MON_MOVES) { - if (GetNoOfHitsToKO(AI_DATA->simulatedDmg[battlerDef][battlerAtk][indexSlot].expected, gBattleMons[battlerAtk].hp) <= nHits) + if (GetNoOfHitsToKO(AI_GetDamage(battlerDef, battlerAtk, indexSlot, AI_DEFENDING, AI_DATA), gBattleMons[battlerAtk].hp) <= nHits) return TRUE; } return FALSE; @@ -1304,7 +1339,7 @@ bool32 CanTargetMoveFaintAi(u32 move, u32 battlerDef, u32 battlerAtk, u32 nHits) // Check if target has means to faint ai mon after modding hp/dmg bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dmgMod) { - u32 i; + u32 moveIndex; u32 unusable = AI_DATA->moveLimitations[battlerDef]; s32 dmg; u16 *moves = gBattleResources->battleHistory->usedMoves[battlerDef]; @@ -1313,13 +1348,13 @@ bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dm if (hpCheck > gBattleMons[battlerAtk].maxHP) hpCheck = gBattleMons[battlerAtk].maxHP; - for (i = 0; i < MAX_MON_MOVES; i++) + for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) { - dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i].expected; + dmg = AI_GetDamage(battlerAtk, battlerDef, moveIndex, AI_DEFENDING, AI_DATA); if (dmgMod) dmg *= dmgMod; - if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & (1u << i)) && dmg >= hpCheck) + if (moves[moveIndex] != MOVE_NONE && moves[moveIndex] != MOVE_UNAVAILABLE && !(unusable & (1u << moveIndex)) && dmg >= hpCheck) { return TRUE; } @@ -2009,17 +2044,29 @@ bool32 ShouldLowerEvasion(u32 battlerAtk, u32 battlerDef, u32 defAbility) return FALSE; } -bool32 CanIndexMoveFaintTarget(u32 battlerAtk, u32 battlerDef, u32 index, u32 numHits) +bool32 CanIndexMoveFaintTarget(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum DamageCalcContext calcContext) { s32 dmg; u16 *moves = gBattleMons[battlerAtk].moves; - if (numHits) - dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][index].expected * numHits; + if (IsDoubleBattle() && battlerDef == BATTLE_PARTNER(battlerAtk)) + dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum; // Attacking partner, be careful else - dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][index].minimum; + dmg = AI_GetDamage(battlerAtk, battlerDef, moveIndex, calcContext, AI_DATA); - if (gBattleMons[battlerDef].hp <= dmg && !CanEndureHit(battlerAtk, battlerDef, moves[index])) + if (gBattleMons[battlerDef].hp <= dmg && !CanEndureHit(battlerAtk, battlerDef, moves[moveIndex])) + return TRUE; + return FALSE; +} + +bool32 CanIndexMoveGuaranteeFaintTarget(u32 battlerAtk, u32 battlerDef, u32 moveIndex) +{ + s32 dmg; + u16 *moves = gBattleMons[battlerAtk].moves; + + dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum; // Explictly care about guaranteed KOs universally + + if (gBattleMons[battlerDef].hp <= dmg && !CanEndureHit(battlerAtk, battlerDef, moves[moveIndex])) return TRUE; return FALSE; } @@ -3362,12 +3409,12 @@ bool32 ShouldAbsorb(u32 battlerAtk, u32 battlerDef, u32 move, s32 damage) return FALSE; } -bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent) +bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent, enum DamageCalcContext calcContext) { if (move == 0xFFFF || AI_IsFaster(battlerAtk, battlerDef, move)) { // using item or user going first - s32 damage = AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex].expected; + s32 damage = AI_GetDamage(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, calcContext, AI_DATA); s32 healAmount = (healPercent * damage) / 100; if (gStatuses3[battlerAtk] & STATUS3_HEAL_BLOCK) healAmount = 0; @@ -3612,7 +3659,7 @@ bool32 ShouldUseWishAromatherapy(u32 battlerAtk, u32 battlerDef, u32 move) switch (GetMoveEffect(move)) { case EFFECT_WISH: - return ShouldRecover(battlerAtk, battlerDef, move, 50); // Switch recovery isn't good idea in doubles + return ShouldRecover(battlerAtk, battlerDef, move, 50, AI_DEFENDING); // Switch recovery isn't good idea in doubles case EFFECT_HEAL_BELL: if (hasStatus) return TRUE; @@ -3638,7 +3685,7 @@ void FreeRestoreBattleMons(struct BattlePokemon *savedBattleMons) } // party logic -s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool32 isPartyMonAttacker, enum DamageRollType rollType) +s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool32 isPartyMonAttacker) { struct SimulatedDamage dmg; uq4_12_t effectiveness; @@ -3659,7 +3706,7 @@ s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct Battl AI_THINKING_STRUCT->saved[battlerAtk].saved = FALSE; } - dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE, AI_GetWeather(), rollType); + dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE, AI_GetWeather()); // restores original gBattleMon struct FreeRestoreBattleMons(savedBattleMons); @@ -3668,7 +3715,7 @@ s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct Battl else SetBattlerAiData(battlerDef, AI_DATA); - return dmg.expected; + return dmg.median; } u32 AI_WhoStrikesFirstPartyMon(u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 moveConsidered) @@ -4034,7 +4081,7 @@ void IncreaseBurnScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) || (!(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_OMNISCIENT) // Not Omniscient but expects physical attacker && gSpeciesInfo[gBattleMons[battlerDef].species].baseAttack >= gSpeciesInfo[gBattleMons[battlerDef].species].baseSpAttack + 10)) { - if (GetMoveCategory(GetBestDmgMoveFromBattler(battlerDef, battlerAtk)) == DAMAGE_CATEGORY_PHYSICAL) + if (GetMoveCategory(GetBestDmgMoveFromBattler(battlerDef, battlerAtk, AI_DEFENDING)) == DAMAGE_CATEGORY_PHYSICAL) ADJUST_SCORE_PTR(DECENT_EFFECT); else ADJUST_SCORE_PTR(WEAK_EFFECT); @@ -4070,7 +4117,7 @@ void IncreaseParalyzeScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) void IncreaseSleepScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) { - if (((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0) && GetMoveEffect(GetBestDmgMoveFromBattler(battlerAtk, battlerDef)) != EFFECT_FOCUS_PUNCH) + if (((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0) && GetMoveEffect(GetBestDmgMoveFromBattler(battlerAtk, battlerDef, AI_ATTACKING)) != EFFECT_FOCUS_PUNCH) || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_SLP || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) return; @@ -4118,7 +4165,7 @@ void IncreaseFrostbiteScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score || (!(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_OMNISCIENT) // Not Omniscient but expects special attacker && gSpeciesInfo[gBattleMons[battlerDef].species].baseSpAttack >= gSpeciesInfo[gBattleMons[battlerDef].species].baseAttack + 10)) { - if (GetMoveCategory(GetBestDmgMoveFromBattler(battlerDef, battlerAtk)) == DAMAGE_CATEGORY_SPECIAL) + if (GetMoveCategory(GetBestDmgMoveFromBattler(battlerDef, battlerAtk, AI_DEFENDING)) == DAMAGE_CATEGORY_SPECIAL) ADJUST_SCORE_PTR(DECENT_EFFECT); else ADJUST_SCORE_PTR(WEAK_EFFECT); @@ -4168,7 +4215,7 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove) else if (!IsBattleMoveStatus(chosenMove) && IsBattleMoveStatus(zMove)) return FALSE; - dmg = AI_CalcDamageSaveBattlers(chosenMove, battlerAtk, battlerDef, &effectiveness, FALSE, DMG_ROLL_DEFAULT); + dmg = AI_CalcDamageSaveBattlers(chosenMove, battlerAtk, battlerDef, &effectiveness, FALSE); if (!IsBattleMoveStatus(chosenMove) && dmg.minimum >= gBattleMons[battlerDef].hp) return FALSE; // don't waste damaging z move if can otherwise faint target @@ -4299,13 +4346,13 @@ u32 IncreaseSubstituteMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) u32 scoreIncrease = 0; if (effect == EFFECT_SUBSTITUTE) // Substitute specific { - if (HasAnyKnownMove(battlerDef) && GetBestDmgFromBattler(battlerDef, battlerAtk) < gBattleMons[battlerAtk].maxHP / 4) + if (HasAnyKnownMove(battlerDef) && GetBestDmgFromBattler(battlerDef, battlerAtk, AI_DEFENDING) < gBattleMons[battlerAtk].maxHP / 4) scoreIncrease += GOOD_EFFECT; } else if (effect == EFFECT_SHED_TAIL) // Shed Tail specific { if ((ShouldPivot(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], move, AI_THINKING_STRUCT->movesetIndex)) - && (HasAnyKnownMove(battlerDef) && (GetBestDmgFromBattler(battlerDef, battlerAtk) < gBattleMons[battlerAtk].maxHP / 2))) + && (HasAnyKnownMove(battlerDef) && (GetBestDmgFromBattler(battlerDef, battlerAtk, AI_DEFENDING) < gBattleMons[battlerAtk].maxHP / 2))) scoreIncrease += BEST_EFFECT; } diff --git a/src/battle_debug.c b/src/battle_debug.c index 44a92b5f93..61635cc815 100644 --- a/src/battle_debug.c +++ b/src/battle_debug.c @@ -946,7 +946,7 @@ static void PutMovesPointsText(struct BattleDebugMenu *data) AddTextPrinterParameterized(data->aiMovesWindowId, FONT_NORMAL, text, 83 + count * 54, i * 15, 0, NULL); ConvertIntToDecimalStringN(text, - AI_DATA->simulatedDmg[data->aiBattlerId][battlerDef][i].expected, + AI_GetDamage(data->aiBattlerId, battlerDef, i, AI_ATTACKING, AI_DATA), STR_CONV_MODE_RIGHT_ALIGN, 3); AddTextPrinterParameterized(data->aiMovesWindowId, FONT_NORMAL, text, 110 + count * 54, i * 15, 0, NULL); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index cfceeb9845..6c73329c8a 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1830,8 +1830,6 @@ static inline u32 GetHoldEffectCritChanceIncrease(u32 battler, u32 holdEffect) return critStageIncrease; } -#define CRITICAL_HIT_BLOCKED -1 -#define CRITICAL_HIT_ALWAYS -2 s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, u32 abilityAtk, u32 abilityDef, u32 holdEffectAtk) { s32 critChance = 0; @@ -1940,8 +1938,6 @@ s32 GetCritHitOdds(s32 critChanceIndex) else return GetCriticalHitOdds(critChanceIndex); } -#undef CRITICAL_HIT_BLOCKED -#undef CRITICAL_HIT_ALWAYS static void Cmd_critcalc(void) {