Fix AI crit calculations (#4751)

This commit is contained in:
Alex 2024-06-10 11:07:31 +02:00 committed by GitHub
commit a894ea4ea6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 57 additions and 18 deletions

View file

@ -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);

View file

@ -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
{

View file

@ -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];

View file

@ -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); }
}
}