From 1ca4676c4c6e5546c5e0c514e831cc6614929c85 Mon Sep 17 00:00:00 2001 From: sneed Date: Sun, 9 Jun 2024 22:30:49 +0300 Subject: [PATCH 1/2] fix ai crit calculations --- include/battle_script_commands.h | 2 +- src/battle_ai_util.c | 16 +++++++++++---- src/battle_script_commands.c | 34 ++++++++++++++++++++------------ 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index 540390bb537..5075d52be73 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -24,7 +24,7 @@ struct PickupItem s32 CalcCritChanceStageArgs(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, u32 abilityAtk, u32 abilityDef, u32 holdEffectAtk); s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility); -s32 GetCritHitChance(s32 critChanceIndex); +s32 GetCritHitOdds(s32 critChanceIndex); u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect); u8 GetBattlerTurnOrderNum(u8 battlerId); bool32 NoAliveMonsForPlayer(void); diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 4524e3b25b5..f8275d417a7 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -515,15 +515,23 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); critChanceIndex = CalcCritChanceStageArgs(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 +1 crit chance + if (critChanceIndex > 1) // Consider crit damage only if a move has at least +2 crit chance { s32 critDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower, effectivenessMultiplier, weather, TRUE, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); - u32 critChance = GetCritHitChance(critChanceIndex); - // With critChance getting closer to 1, dmg gets closer to critDmg. - dmg = LowestRollDmg((critDmg + normalDmg * (critChance - 1)) / (critChance)); + u32 critOdds = GetCritHitOdds(critChanceIndex); // Crit chance is 1/critOdds + // With critOdds getting closer to 1, dmg gets closer to critDmg. + dmg = LowestRollDmg((critDmg + normalDmg * (critOdds - 1)) / (critOdds)); + } + else if (critChanceIndex == -2) // Guaranteed critical + { + s32 critDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower, + effectivenessMultiplier, weather, TRUE, + aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], + aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); + dmg = LowestRollDmg(critDmg); } else { diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index e9679239317..d2274562249 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1905,11 +1905,11 @@ static void Cmd_ppreduce(void) // The chance is 1/N for each stage. #if B_CRIT_CHANCE >= GEN_7 - static const u8 sCriticalHitChance[] = {24, 8, 2, 1, 1}; + static const u8 sCriticalHitOdds[] = {24, 8, 2, 1, 1}; #elif B_CRIT_CHANCE == GEN_6 - static const u8 sCriticalHitChance[] = {16, 8, 2, 1, 1}; + static const u8 sCriticalHitOdds[] = {16, 8, 2, 1, 1}; #else - static const u8 sCriticalHitChance[] = {16, 8, 4, 3, 2}; // Gens 2,3,4,5 + static const u8 sCriticalHitOdds[] = {16, 8, 4, 3, 2}; // Gens 2,3,4,5 #endif // B_CRIT_CHANCE #define BENEFITS_FROM_LEEK(battler, holdEffect)((holdEffect == HOLD_EFFECT_LEEK) && (GET_BASE_SPECIES_ID(gBattleMons[battler].species) == SPECIES_FARFETCHD || gBattleMons[battler].species == SPECIES_SIRFETCHD)) @@ -1917,8 +1917,7 @@ s32 CalcCritChanceStageArgs(u32 battlerAtk, u32 battlerDef, u32 move, bool32 rec { s32 critChance = 0; - if (gSideStatuses[battlerDef] & SIDE_STATUS_LUCKY_CHANT - || abilityDef == ABILITY_BATTLE_ARMOR || abilityDef == ABILITY_SHELL_ARMOR) + if (gSideStatuses[battlerDef] & SIDE_STATUS_LUCKY_CHANT) { critChance = -1; } @@ -1940,12 +1939,21 @@ s32 CalcCritChanceStageArgs(u32 battlerAtk, u32 battlerDef, u32 move, bool32 rec + (abilityAtk == ABILITY_SUPER_LUCK) + gBattleStruct->bonusCritStages[gBattlerAttacker]; - // Record ability only if move had at least +3 chance to get a crit - if (critChance >= 3 && recordAbility && (abilityDef == ABILITY_BATTLE_ARMOR || abilityDef == ABILITY_SHELL_ARMOR)) - RecordAbilityBattle(battlerDef, abilityDef); + if (critChance >= ARRAY_COUNT(sCriticalHitOdds)) + critChance = ARRAY_COUNT(sCriticalHitOdds) - 1; + } - if (critChance >= ARRAY_COUNT(sCriticalHitChance)) - critChance = ARRAY_COUNT(sCriticalHitChance) - 1; + if (critChance != -1 && (abilityDef == ABILITY_BATTLE_ARMOR || abilityDef == ABILITY_SHELL_ARMOR)) + { + // Record ability only if move had 100% chance to get a crit + if (recordAbility) + { + if (critChance == -2) + RecordAbilityBattle(battlerDef, abilityDef); + else if (sCriticalHitOdds[critChance] == 1) + RecordAbilityBattle(battlerDef, abilityDef); + } + critChance = -1; } return critChance; @@ -1960,12 +1968,12 @@ s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordA } #undef BENEFITS_FROM_LEEK -s32 GetCritHitChance(s32 critChanceIndex) +s32 GetCritHitOdds(s32 critChanceIndex) { if (critChanceIndex < 0) return -1; else - return sCriticalHitChance[critChanceIndex]; + return sCriticalHitOdds[critChanceIndex]; } static void Cmd_critcalc(void) @@ -1983,7 +1991,7 @@ static void Cmd_critcalc(void) else if (critChance == -2) gIsCriticalHit = TRUE; else - gIsCriticalHit = RandomWeighted(RNG_CRITICAL_HIT, sCriticalHitChance[critChance] - 1, 1); + gIsCriticalHit = RandomWeighted(RNG_CRITICAL_HIT, sCriticalHitOdds[critChance] - 1, 1); // Counter for EVO_CRITICAL_HITS. partySlot = gBattlerPartyIndexes[gBattlerAttacker]; From aa50bfef28795b9a8caca0248cd274bbef5395ff Mon Sep 17 00:00:00 2001 From: sneed Date: Sun, 9 Jun 2024 23:41:12 +0300 Subject: [PATCH 2/2] add test --- test/battle/ai.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/battle/ai.c b/test/battle/ai.c index 28e541befd5..819eb2edaa7 100644 --- a/test/battle/ai.c +++ b/test/battle/ai.c @@ -955,3 +955,26 @@ AI_DOUBLE_BATTLE_TEST("AI will the see a corresponding absorbing ability on part TURN { EXPECT_MOVE(opponentLeft, MOVE_TACKLE); } } } + +AI_SINGLE_BATTLE_TEST("AI calculates guaranteed criticals and detects critical immunity") +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_SWIFT_SWIM; } + PARAMETRIZE { ability = ABILITY_SHELL_ARMOR; } + + GIVEN { + ASSUME(gMovesInfo[MOVE_STORM_THROW].alwaysCriticalHit); + ASSUME(gMovesInfo[MOVE_STORM_THROW].power == 60); + ASSUME(gMovesInfo[MOVE_BRICK_BREAK].power == 75); + ASSUME(gMovesInfo[MOVE_STORM_THROW].type == gMovesInfo[MOVE_BRICK_BREAK].type); + ASSUME(gMovesInfo[MOVE_STORM_THROW].category == gMovesInfo[MOVE_BRICK_BREAK].category); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_OMASTAR) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_STORM_THROW, MOVE_BRICK_BREAK); } + } WHEN { + if (ability == ABILITY_SHELL_ARMOR) + TURN { EXPECT_MOVE(opponent, MOVE_BRICK_BREAK); } + else + TURN { EXPECT_MOVE(opponent, MOVE_STORM_THROW); } + } +}