AI calculates/stores minimum possible move damage and uses it with AI_FLAG_TRY_TO_FAINT (#4760)
* AI uses safest option to faint foes * dont calculate non crit dmg unless it's needed * struct SimulatedDamage * add GetDamageByRollType and name DamageRollType * Parametrize test AI flags * use simDamage member's instead of dmg & minDmg
This commit is contained in:
parent
64f82cdd5f
commit
821d5dccab
7 changed files with 148 additions and 76 deletions
|
@ -344,6 +344,12 @@ struct SwitchinCandidate
|
|||
bool8 hypotheticalStatus;
|
||||
};
|
||||
|
||||
struct SimulatedDamage
|
||||
{
|
||||
s32 expected;
|
||||
s32 minimum;
|
||||
};
|
||||
|
||||
// Ai Data used when deciding which move to use, computed only once before each turn's start.
|
||||
struct AiLogicData
|
||||
{
|
||||
|
@ -355,7 +361,7 @@ struct AiLogicData
|
|||
u8 hpPercents[MAX_BATTLERS_COUNT];
|
||||
u16 partnerMove;
|
||||
u16 speedStats[MAX_BATTLERS_COUNT]; // Speed stats for all battles, calculated only once, same way as damages
|
||||
s32 simulatedDmg[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
|
||||
struct SimulatedDamage simulatedDmg[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
|
||||
u8 effectiveness[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
|
||||
u8 moveAccuracy[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
|
||||
u8 moveLimitations[MAX_BATTLERS_COUNT];
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#define MIN_ROLL_PERCENTAGE DMG_ROLL_PERCENT_LO
|
||||
#define DMG_ROLL_PERCENTAGE ((MAX_ROLL_PERCENTAGE + MIN_ROLL_PERCENTAGE + 1) / 2) // Controls the damage roll the AI sees for the default roll. By default the 9th roll is seen
|
||||
|
||||
enum
|
||||
enum DamageRollType
|
||||
{
|
||||
DMG_ROLL_LOWEST,
|
||||
DMG_ROLL_DEFAULT,
|
||||
|
@ -94,8 +94,8 @@ 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);
|
||||
s32 AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 dmgRoll);
|
||||
s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather, u32 dmgRoll);
|
||||
struct SimulatedDamage AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, enum DamageRollType rollType);
|
||||
struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather, enum DamageRollType rollType);
|
||||
bool32 AI_IsDamagedByRecoil(u32 battler);
|
||||
u32 GetNoOfHitsToKO(u32 dmg, s32 hp);
|
||||
u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef);
|
||||
|
@ -199,8 +199,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, u32 dmgRoll);
|
||||
s32 AI_CheckMoveEffects(u32 battlerAtk, u32 battlerDef, u32 move, s32 score, struct AiLogicData *aiData, u32 predictedMove, bool32 isDoubleBattle);
|
||||
s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool32 isPartyMonAttacker, enum DamageRollType rollType);
|
||||
s32 AI_TryToClearStats(u32 battlerAtk, u32 battlerDef, bool32 isDoubleBattle);
|
||||
bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef);
|
||||
bool32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData);
|
||||
|
|
|
@ -450,7 +450,7 @@ static void SetBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u3
|
|||
SetBattlerData(battlerDef);
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
s32 dmg = 0;
|
||||
struct SimulatedDamage dmg;
|
||||
u8 effectiveness = AI_EFFECTIVENESS_x0;
|
||||
u32 move = moves[i];
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult)
|
|||
hasSuperEffectiveMove = TRUE;
|
||||
|
||||
// Get maximum damage mon can deal
|
||||
damageDealt = AI_DATA->simulatedDmg[battler][opposingBattler][i];
|
||||
damageDealt = AI_DATA->simulatedDmg[battler][opposingBattler][i].expected;
|
||||
if(damageDealt > maxDamageDealt)
|
||||
{
|
||||
maxDamageDealt = damageDealt;
|
||||
|
@ -148,8 +148,8 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult)
|
|||
playerMove = gBattleMons[opposingBattler].moves[i];
|
||||
if (playerMove != MOVE_NONE && gMovesInfo[playerMove].power != 0)
|
||||
{
|
||||
damageTaken = AI_CalcDamage(playerMove, opposingBattler, battler, &effectiveness, FALSE, weather, DMG_ROLL_HIGHEST);
|
||||
if (damageTaken > maxDamageTaken)
|
||||
struct SimulatedDamage dmg = AI_CalcDamage(playerMove, opposingBattler, battler, &effectiveness, FALSE, weather, DMG_ROLL_HIGHEST);
|
||||
if (dmg.expected > maxDamageTaken)
|
||||
maxDamageTaken = damageTaken;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -365,14 +365,14 @@ 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.
|
||||
s32 AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 dmgRoll)
|
||||
struct SimulatedDamage AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, enum DamageRollType rollType)
|
||||
{
|
||||
s32 dmg = 0;
|
||||
struct SimulatedDamage dmg;
|
||||
SaveBattlerData(battlerAtk);
|
||||
SaveBattlerData(battlerDef);
|
||||
SetBattlerData(battlerAtk);
|
||||
SetBattlerData(battlerDef);
|
||||
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, typeEffectiveness, considerZPower, AI_GetWeather(AI_DATA), dmgRoll);
|
||||
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, typeEffectiveness, considerZPower, AI_GetWeather(AI_DATA), rollType);
|
||||
RestoreBattlerData(battlerAtk);
|
||||
RestoreBattlerData(battlerDef);
|
||||
return dmg;
|
||||
|
@ -492,9 +492,20 @@ bool32 IsDamageMoveUnusable(u32 move, u32 battlerAtk, u32 battlerDef)
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather, u32 dmgRoll)
|
||||
static inline s32 GetDamageByRollType(s32 dmg, enum DamageRollType rollType)
|
||||
{
|
||||
s32 dmg, moveType;
|
||||
if (rollType == DMG_ROLL_LOWEST)
|
||||
return LowestRollDmg(dmg);
|
||||
else if (rollType == DMG_ROLL_HIGHEST)
|
||||
return HighestRollDmg(dmg);
|
||||
else
|
||||
return DmgRoll(dmg);
|
||||
}
|
||||
|
||||
struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather, enum DamageRollType rollType)
|
||||
{
|
||||
struct SimulatedDamage simDamage;
|
||||
s32 moveType;
|
||||
uq4_12_t effectivenessMultiplier;
|
||||
bool32 isDamageMoveUnusable = FALSE;
|
||||
bool32 toggledDynamax = FALSE;
|
||||
|
@ -537,7 +548,7 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes
|
|||
|
||||
if (gMovesInfo[move].power && !isDamageMoveUnusable)
|
||||
{
|
||||
s32 critChanceIndex, normalDmg, fixedBasePower, n;
|
||||
s32 critChanceIndex, fixedBasePower, n;
|
||||
|
||||
ProteanTryChangeType(battlerAtk, aiData->abilities[battlerAtk], move, moveType);
|
||||
// Certain moves like Rollout calculate damage based on values which change during the move execution, but before calling dmg calc.
|
||||
|
@ -554,48 +565,46 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes
|
|||
fixedBasePower = 0;
|
||||
break;
|
||||
}
|
||||
normalDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower,
|
||||
effectivenessMultiplier, weather, FALSE,
|
||||
aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef],
|
||||
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 +2 crit chance
|
||||
{
|
||||
s32 nonCritDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower,
|
||||
effectivenessMultiplier, weather, FALSE,
|
||||
aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef],
|
||||
aiData->abilities[battlerAtk], aiData->abilities[battlerDef]);
|
||||
s32 critDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower,
|
||||
effectivenessMultiplier, weather, TRUE,
|
||||
aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef],
|
||||
aiData->abilities[battlerAtk], aiData->abilities[battlerDef]);
|
||||
effectivenessMultiplier, weather, TRUE,
|
||||
aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef],
|
||||
aiData->abilities[battlerAtk], aiData->abilities[battlerDef]);
|
||||
|
||||
u32 critOdds = GetCritHitOdds(critChanceIndex);
|
||||
// With critOdds getting closer to 1, dmg gets closer to critDmg.
|
||||
if (dmgRoll == DMG_ROLL_DEFAULT)
|
||||
dmg = DmgRoll((critDmg + normalDmg * (critOdds - 1)) / (critOdds));
|
||||
else if (dmgRoll == DMG_ROLL_HIGHEST)
|
||||
dmg = HighestRollDmg((critDmg + normalDmg * (critOdds - 1)) / (critOdds));
|
||||
simDamage.expected = GetDamageByRollType((critDmg + nonCritDmg * (critOdds - 1)) / critOdds, rollType);
|
||||
if (critOdds == 1)
|
||||
simDamage.minimum = LowestRollDmg(critDmg);
|
||||
else
|
||||
dmg = LowestRollDmg((critDmg + normalDmg * (critOdds - 1)) / (critOdds)); // Default to lowest roll
|
||||
simDamage.minimum = LowestRollDmg(nonCritDmg);
|
||||
}
|
||||
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]);
|
||||
if (dmgRoll == DMG_ROLL_DEFAULT)
|
||||
dmg = DmgRoll(critDmg);
|
||||
else if (dmgRoll == DMG_ROLL_HIGHEST)
|
||||
dmg = HighestRollDmg(critDmg);
|
||||
else
|
||||
dmg = LowestRollDmg(critDmg); // Default to lowest roll
|
||||
effectivenessMultiplier, weather, TRUE,
|
||||
aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef],
|
||||
aiData->abilities[battlerAtk], aiData->abilities[battlerDef]);
|
||||
|
||||
simDamage.expected = GetDamageByRollType(critDmg, rollType);
|
||||
simDamage.minimum = LowestRollDmg(critDmg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dmgRoll == DMG_ROLL_DEFAULT)
|
||||
dmg = DmgRoll(normalDmg);
|
||||
else if (dmgRoll == DMG_ROLL_HIGHEST)
|
||||
dmg = HighestRollDmg(normalDmg);
|
||||
else
|
||||
dmg = LowestRollDmg(normalDmg); // Default to lowest roll
|
||||
s32 nonCritDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower,
|
||||
effectivenessMultiplier, weather, FALSE,
|
||||
aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef],
|
||||
aiData->abilities[battlerAtk], aiData->abilities[battlerDef]);
|
||||
|
||||
simDamage.expected = GetDamageByRollType(nonCritDmg, rollType);
|
||||
simDamage.minimum = LowestRollDmg(nonCritDmg);
|
||||
}
|
||||
|
||||
if (!gBattleStruct->zmove.active)
|
||||
|
@ -604,33 +613,49 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes
|
|||
switch (gMovesInfo[move].effect)
|
||||
{
|
||||
case EFFECT_LEVEL_DAMAGE:
|
||||
simDamage.expected = simDamage.minimum = gBattleMons[battlerAtk].level * (aiData->abilities[battlerAtk] == ABILITY_PARENTAL_BOND ? 2 : 1);
|
||||
break;
|
||||
case EFFECT_PSYWAVE:
|
||||
dmg = gBattleMons[battlerAtk].level * (aiData->abilities[battlerAtk] == ABILITY_PARENTAL_BOND ? 2 : 1);
|
||||
simDamage.expected = gBattleMons[battlerAtk].level * (aiData->abilities[battlerAtk] == ABILITY_PARENTAL_BOND ? 2 : 1);
|
||||
simDamage.minimum = simDamage.expected / 2;
|
||||
break;
|
||||
case EFFECT_FIXED_DAMAGE_ARG:
|
||||
dmg = gMovesInfo[move].argument * (aiData->abilities[battlerAtk] == ABILITY_PARENTAL_BOND ? 2 : 1);
|
||||
simDamage.expected = simDamage.minimum = gMovesInfo[move].argument * (aiData->abilities[battlerAtk] == ABILITY_PARENTAL_BOND ? 2 : 1);
|
||||
break;
|
||||
case EFFECT_MULTI_HIT:
|
||||
if (move == MOVE_WATER_SHURIKEN && gBattleMons[battlerAtk].species == SPECIES_GRENINJA_ASH)
|
||||
dmg *= 3;
|
||||
{
|
||||
simDamage.expected *= 3;
|
||||
simDamage.minimum *= 3;
|
||||
}
|
||||
else if (aiData->abilities[battlerAtk] == ABILITY_SKILL_LINK)
|
||||
dmg *= 5;
|
||||
{
|
||||
simDamage.expected *= 5;
|
||||
simDamage.minimum *= 5;
|
||||
}
|
||||
else if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_LOADED_DICE)
|
||||
dmg *= 4;
|
||||
{
|
||||
simDamage.expected *= 9;
|
||||
simDamage.expected /= 2;
|
||||
simDamage.minimum *= 4;
|
||||
}
|
||||
else
|
||||
dmg *= 3;
|
||||
{
|
||||
simDamage.expected *= 3;
|
||||
simDamage.minimum *= 2;
|
||||
}
|
||||
break;
|
||||
case EFFECT_ENDEAVOR:
|
||||
// If target has less HP than user, Endeavor does no damage
|
||||
dmg = max(0, gBattleMons[battlerDef].hp - gBattleMons[battlerAtk].hp);
|
||||
simDamage.expected = simDamage.minimum = max(0, gBattleMons[battlerDef].hp - gBattleMons[battlerAtk].hp);
|
||||
break;
|
||||
case EFFECT_SUPER_FANG:
|
||||
dmg = (aiData->abilities[battlerAtk] == ABILITY_PARENTAL_BOND
|
||||
simDamage.expected = simDamage.minimum = (aiData->abilities[battlerAtk] == ABILITY_PARENTAL_BOND
|
||||
? max(2, gBattleMons[battlerDef].hp * 3 / 4)
|
||||
: max(1, gBattleMons[battlerDef].hp / 2));
|
||||
break;
|
||||
case EFFECT_FINAL_GAMBIT:
|
||||
dmg = gBattleMons[battlerAtk].hp;
|
||||
simDamage.expected = simDamage.minimum = gBattleMons[battlerAtk].hp;
|
||||
break;
|
||||
case EFFECT_BEAT_UP:
|
||||
if (B_BEAT_UP >= GEN_5)
|
||||
|
@ -638,11 +663,12 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes
|
|||
u32 partyCount = CalculatePartyCount(GetBattlerParty(battlerAtk));
|
||||
u32 i;
|
||||
gBattleStruct->beatUpSlot = 0;
|
||||
dmg = 0;
|
||||
simDamage.expected = 0;
|
||||
for (i = 0; i < partyCount; i++)
|
||||
{
|
||||
dmg += CalculateMoveDamage(move, battlerAtk, battlerDef, moveType, 0, FALSE, FALSE, FALSE);
|
||||
simDamage.expected += CalculateMoveDamage(move, battlerAtk, battlerDef, moveType, 0, FALSE, FALSE, FALSE);
|
||||
}
|
||||
simDamage.minimum = simDamage.expected;
|
||||
gBattleStruct->beatUpSlot = 0;
|
||||
}
|
||||
break;
|
||||
|
@ -650,15 +676,21 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes
|
|||
|
||||
// Handle other multi-strike moves
|
||||
if (gMovesInfo[move].strikeCount > 1 && gMovesInfo[move].effect != EFFECT_TRIPLE_KICK)
|
||||
dmg *= gMovesInfo[move].strikeCount;
|
||||
{
|
||||
simDamage.expected *= gMovesInfo[move].strikeCount;
|
||||
simDamage.minimum *= gMovesInfo[move].strikeCount;
|
||||
}
|
||||
|
||||
if (dmg == 0)
|
||||
dmg = 1;
|
||||
if (simDamage.expected == 0)
|
||||
simDamage.expected = 1;
|
||||
if (simDamage.minimum == 0)
|
||||
simDamage.minimum = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dmg = 0;
|
||||
simDamage.expected = 0;
|
||||
simDamage.minimum = 0;
|
||||
}
|
||||
|
||||
// convert multiper to AI_EFFECTIVENESS_xX
|
||||
|
@ -673,7 +705,7 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes
|
|||
if (toggledTera)
|
||||
gBattleStruct->tera.isTerastallized[GetBattlerSide(battlerAtk)] &= ~(gBitTable[gBattlerPartyIndexes[battlerAtk]]);
|
||||
|
||||
return dmg;
|
||||
return simDamage;
|
||||
}
|
||||
|
||||
bool32 AI_IsDamagedByRecoil(u32 battler)
|
||||
|
@ -930,12 +962,12 @@ u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef)
|
|||
|
||||
u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex)
|
||||
{
|
||||
return GetNoOfHitsToKOBattlerDmg(AI_DATA->simulatedDmg[battlerAtk][battlerDef][moveIndex], battlerDef);
|
||||
return GetNoOfHitsToKOBattlerDmg(AI_DATA->simulatedDmg[battlerAtk][battlerDef][moveIndex].expected, battlerDef);
|
||||
}
|
||||
|
||||
u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef)
|
||||
{
|
||||
int bestDmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex];
|
||||
int bestDmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex].expected;
|
||||
|
||||
return (bestDmg * 100) / gBattleMons[battlerDef].maxHP;
|
||||
}
|
||||
|
@ -1026,7 +1058,7 @@ bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk)
|
|||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & gBitTable[i])
|
||||
&& AI_DATA->simulatedDmg[battlerDef][battlerAtk][i] >= gBattleMons[battlerAtk].hp)
|
||||
&& AI_DATA->simulatedDmg[battlerDef][battlerAtk][i].expected >= gBattleMons[battlerAtk].hp)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
@ -1064,9 +1096,9 @@ u32 GetBestDmgMoveFromBattler(u32 battlerAtk, u32 battlerDef)
|
|||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & gBitTable[i])
|
||||
&& bestDmg < AI_DATA->simulatedDmg[battlerAtk][battlerDef][i])
|
||||
&& bestDmg < AI_DATA->simulatedDmg[battlerAtk][battlerDef][i].expected)
|
||||
{
|
||||
bestDmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i];
|
||||
bestDmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i].expected;
|
||||
move = moves[i];
|
||||
}
|
||||
}
|
||||
|
@ -1086,7 +1118,7 @@ bool32 CanAIFaintTarget(u32 battlerAtk, u32 battlerDef, u32 numHits)
|
|||
if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(moveLimitations & gBitTable[i]))
|
||||
{
|
||||
// Use the pre-calculated value in simulatedDmg instead of re-calculating it
|
||||
dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i];
|
||||
dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i].expected;
|
||||
|
||||
if (numHits)
|
||||
dmg *= numHits;
|
||||
|
@ -1104,7 +1136,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], gBattleMons[battlerAtk].hp) <= nHits)
|
||||
if (GetNoOfHitsToKO(AI_DATA->simulatedDmg[battlerDef][battlerAtk][indexSlot].expected, gBattleMons[battlerAtk].hp) <= nHits)
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
@ -1124,7 +1156,7 @@ bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dm
|
|||
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i];
|
||||
dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i].expected;
|
||||
if (dmgMod)
|
||||
dmg *= dmgMod;
|
||||
|
||||
|
@ -1812,10 +1844,12 @@ bool32 ShouldLowerEvasion(u32 battlerAtk, u32 battlerDef, u32 defAbility)
|
|||
|
||||
bool32 CanIndexMoveFaintTarget(u32 battlerAtk, u32 battlerDef, u32 index, u32 numHits)
|
||||
{
|
||||
s32 dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][index];
|
||||
s32 dmg;
|
||||
|
||||
if (numHits)
|
||||
dmg *= numHits;
|
||||
dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][index].expected * numHits;
|
||||
else
|
||||
dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][index].minimum;
|
||||
|
||||
if (gBattleMons[battlerDef].hp <= dmg)
|
||||
return TRUE;
|
||||
|
@ -3079,7 +3113,7 @@ bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent)
|
|||
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];
|
||||
s32 damage = AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex].expected;
|
||||
s32 healAmount = (healPercent * damage) / 100;
|
||||
if (gStatuses3[battlerAtk] & STATUS3_HEAL_BLOCK)
|
||||
healAmount = 0;
|
||||
|
@ -3331,9 +3365,9 @@ void FreeRestoreBattleMons(struct BattlePokemon *savedBattleMons)
|
|||
}
|
||||
|
||||
// party logic
|
||||
s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool32 isPartyMonAttacker, u32 dmgRoll)
|
||||
s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool32 isPartyMonAttacker, enum DamageRollType rollType)
|
||||
{
|
||||
s32 dmg;
|
||||
struct SimulatedDamage dmg;
|
||||
u8 effectiveness;
|
||||
struct BattlePokemon *savedBattleMons = AllocSaveBattleMons();
|
||||
|
||||
|
@ -3352,10 +3386,10 @@ 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(AI_DATA), dmgRoll);
|
||||
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE, AI_GetWeather(AI_DATA), rollType);
|
||||
// restores original gBattleMon struct
|
||||
FreeRestoreBattleMons(savedBattleMons);
|
||||
return dmg;
|
||||
return dmg.expected;
|
||||
}
|
||||
|
||||
s32 CountUsablePartyMons(u32 battlerId)
|
||||
|
@ -3784,6 +3818,7 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove)
|
|||
if (IsViableZMove(battlerAtk, chosenMove))
|
||||
{
|
||||
u8 effectiveness;
|
||||
struct SimulatedDamage dmg;
|
||||
|
||||
if (gBattleMons[battlerDef].ability == ABILITY_DISGUISE
|
||||
&& (gBattleMons[battlerDef].species == SPECIES_MIMIKYU_DISGUISED || gBattleMons[battlerDef].species == SPECIES_MIMIKYU_TOTEM_DISGUISED))
|
||||
|
@ -3796,7 +3831,9 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove)
|
|||
else if (!IS_MOVE_STATUS(chosenMove) && IS_MOVE_STATUS(gBattleStruct->zmove.chosenZMove))
|
||||
return FALSE;
|
||||
|
||||
if (!IS_MOVE_STATUS(chosenMove) && AI_CalcDamageSaveBattlers(chosenMove, battlerAtk, battlerDef, &effectiveness, FALSE, DMG_ROLL_DEFAULT) >= gBattleMons[battlerDef].hp)
|
||||
dmg = AI_CalcDamageSaveBattlers(chosenMove, battlerAtk, battlerDef, &effectiveness, FALSE, DMG_ROLL_DEFAULT);
|
||||
|
||||
if (!IS_MOVE_STATUS(chosenMove) && dmg.minimum >= gBattleMons[battlerDef].hp)
|
||||
return FALSE; // don't waste damaging z move if can otherwise faint target
|
||||
|
||||
return TRUE;
|
||||
|
|
|
@ -752,7 +752,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],
|
||||
AI_DATA->simulatedDmg[data->aiBattlerId][battlerDef][i].expected,
|
||||
STR_CONV_MODE_RIGHT_ALIGN, 3);
|
||||
AddTextPrinterParameterized(data->aiMovesWindowId, FONT_NORMAL, text, 110 + count * 54, i * 15, 0, NULL);
|
||||
|
||||
|
|
|
@ -714,3 +714,33 @@ AI_SINGLE_BATTLE_TEST("AI calculates guaranteed criticals and detects critical i
|
|||
TURN { EXPECT_MOVE(opponent, MOVE_STORM_THROW); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI uses a guaranteed KO move instead of the move with the highest expected damage")
|
||||
{
|
||||
u32 flags;
|
||||
|
||||
PARAMETRIZE { flags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY; }
|
||||
PARAMETRIZE { flags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT; }
|
||||
|
||||
GIVEN {
|
||||
ASSUME(gMovesInfo[MOVE_SLASH].criticalHitStage == 1);
|
||||
ASSUME(gMovesInfo[MOVE_SLASH].power == 70);
|
||||
ASSUME(gMovesInfo[MOVE_STRENGTH].power == 80);
|
||||
ASSUME(gMovesInfo[MOVE_SLASH].type == gMovesInfo[MOVE_STRENGTH].type);
|
||||
ASSUME(gMovesInfo[MOVE_SLASH].category == gMovesInfo[MOVE_STRENGTH].category);
|
||||
AI_FLAGS(flags);
|
||||
PLAYER(SPECIES_WOBBUFFET) { HP(225); }
|
||||
OPPONENT(SPECIES_ABSOL) { Ability(ABILITY_SUPER_LUCK); Moves(MOVE_SLASH, MOVE_STRENGTH); }
|
||||
} WHEN {
|
||||
TURN { EXPECT_MOVE(opponent, MOVE_SLASH); }
|
||||
if (flags & AI_FLAG_TRY_TO_FAINT)
|
||||
TURN { EXPECT_MOVE(opponent, MOVE_STRENGTH); }
|
||||
else
|
||||
TURN { EXPECT_MOVE(opponent, MOVE_SLASH); }
|
||||
} SCENE {
|
||||
if (flags & AI_FLAG_TRY_TO_FAINT)
|
||||
MESSAGE("Wobbuffet fainted!");
|
||||
else
|
||||
NOT MESSAGE("Wobbuffet fainted!");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue