mirror of
https://github.com/pret/pokeemerald.git
synced 2026-04-26 01:19:29 -05:00
Fix roll handling in AI damage calcs (#6396)
Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
This commit is contained in:
parent
46b821c196
commit
b12f32667f
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user