Improve AI's priority handling (#7337)

This commit is contained in:
Pawkkie 2025-07-26 12:22:47 -04:00 committed by GitHub
parent cf4bb3e286
commit 57fda0d060
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 353 additions and 146 deletions

View File

@ -45,6 +45,13 @@ enum AIScore
WORST_EFFECT = -10
};
enum MoveComparisonResult
{
MOVE_NEUTRAL_COMPARISON,
MOVE_WON_COMPARISON,
MOVE_LOST_COMPARISON,
};
// AI_TryToFaint
#define FAST_KILL 6 // AI is faster and faints target
#define SLOW_KILL 4 // AI is slower and faints target

View File

@ -29,8 +29,10 @@ enum DamageCalcContext
enum AiCompareMovesPriority
{
PRIORITY_EFFECT,
PRIORITY_GUARANTEE,
PRIORITY_ACCURACY,
PRIORITY_NOT_CHARGING
PRIORITY_NOT_CHARGING,
PRIORITY_SPEED,
};
enum AIPivot
@ -54,6 +56,12 @@ enum AIConsiderGimmick
USE_GIMMICK,
};
enum ConsiderPriority
{
DONT_CONSIDER_PRIORITY,
CONSIDER_PRIORITY,
};
static inline bool32 IsMoveUnusable(u32 moveIndex, u32 move, u32 moveLimitations)
{
return move == MOVE_NONE
@ -63,8 +71,10 @@ static inline bool32 IsMoveUnusable(u32 moveIndex, u32 move, u32 moveLimitations
typedef bool32 (*MoveFlag)(u32 move);
bool32 AI_IsFaster(u32 battlerAi, u32 battlerDef, u32 move);
bool32 AI_IsSlower(u32 battlerAi, u32 battlerDef, u32 move);
bool32 AI_IsFaster(u32 battlerAi, u32 battlerDef, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority);
bool32 AI_IsSlower(u32 battlerAi, u32 battlerDef, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority);
bool32 AI_IsPartyMonFaster(u32 battlerAi, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority);
bool32 AI_IsPartyMonSlower(u32 battlerAi, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority);
bool32 AI_RandLessThan(u32 val);
u32 AI_GetDamage(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum DamageCalcContext calcContext, struct AiLogicData *aiData);
bool32 IsAiVsAiBattle(void);
@ -92,7 +102,7 @@ bool32 AI_BattlerAtMaxHp(u32 battler);
u32 GetHealthPercentage(u32 battler);
bool32 AI_CanBattlerEscape(u32 battler);
bool32 IsBattlerTrapped(u32 battlerAtk, u32 battlerDef);
s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 moveConsidered);
s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 aiMoveConsidered, u32 playerMoveConsidered, enum ConsiderPriority considerPriority);
bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk);
u32 NoOfHitsForTargetToFaintBattler(u32 battlerDef, u32 battlerAtk);
u32 GetBestDmgMoveFromBattler(u32 battlerAtk, u32 battlerDef, enum DamageCalcContext calcContext);
@ -107,7 +117,6 @@ u32 AI_GetSwitchinWeather(struct BattlePokemon battleMon);
enum WeatherState IsWeatherActive(u32 flags);
bool32 CanAIFaintTarget(u32 battlerAtk, u32 battlerDef, 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);
@ -131,6 +140,7 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove);
void SetAIUsingGimmick(u32 battler, enum AIConsiderGimmick use);
bool32 IsAIUsingGimmick(u32 battler);
void DecideTerastal(u32 battler);
bool32 CanEndureHit(u32 battler, u32 battlerTarget, u32 move);
// stat stage checks
bool32 AnyStatIsRaised(u32 battlerId);
@ -143,7 +153,7 @@ u32 CountNegativeStatStages(u32 battlerId);
// move checks
bool32 IsAffectedByPowder(u32 battler, u32 ability, enum ItemHoldEffect holdEffect);
bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, enum DamageCategory category);
s32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo);
enum MoveComparisonResult 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, enum AIConsiderGimmick considerGimmickAtk, enum AIConsiderGimmick considerGimmickDef);
struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, enum AIConsiderGimmick considerGimmickAtk, enum AIConsiderGimmick considerGimmickDef, u32 weather);
bool32 AI_IsDamagedByRecoil(u32 battler);
@ -269,7 +279,7 @@ 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, enum DamageCalcContext calcContext);
u32 AI_WhoStrikesFirstPartyMon(u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 moveConsidered);
u32 AI_WhoStrikesFirstPartyMon(u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 aiMoveConsidered, u32 playerMoveConsidered, enum ConsiderPriority ConsiderPriority);
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);

View File

@ -60,6 +60,7 @@
#define ENABLE_RECOVERY_THRESHOLD 60 // HP percentage beneath which SHOULD_RECOVER_CHANCE is active
#define SUCKER_PUNCH_CHANCE 50 // Chance for the AI to not use Sucker Punch if the player has a status move
#define SUCKER_PUNCH_PREDICTION_CHANCE 50 // Additional chance for the AI to not use Sucker Punch if actively predicting a status move if SUCKER_PUNCH_CHANCE fails
#define PRIORITIZE_LAST_CHANCE_CHANCE 50 // Chance the AI will prioritize Last Chance (priority move in the face of being outsped and KO'd) over Slow KO
// AI damage calc considerations
#define RISKY_AI_CRIT_STAGE_THRESHOLD 2 // Stat stages at which Risky will assume it gets a crit

View File

@ -202,6 +202,7 @@ enum RandomTag
RNG_AI_STATUS_FOCUS_PUNCH,
RNG_AI_BOOST_INTO_HAZE,
RNG_AI_SHOULD_RECOVER,
RNG_AI_PRIORITIZE_LAST_CHANCE,
RNG_HEALER,
RNG_DEXNAV_ENCOUNTER_LEVEL,
RNG_AI_ASSUME_STATUS_SLEEP,

View File

@ -1083,7 +1083,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (IsPowderMove(move) && !IsAffectedByPowder(battlerDef, aiData->abilities[battlerDef], aiData->holdEffects[battlerDef]))
RETURN_SCORE_MINUS(10);
if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_IsFaster(battlerAtk, battlerDef, move))
if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
RETURN_SCORE_MINUS(10);
if (IsTwoTurnNotSemiInvulnerableMove(battlerAtk, move) && CanTargetFaintAi(battlerDef, battlerAtk))
@ -1309,7 +1309,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
switch (moveEffect)
{
case EFFECT_HIT: // only applies to Vital Throw
if (GetBattleMovePriority(battlerAtk, aiData->abilities[battlerAtk], move) < 0 && AI_IsFaster(battlerAtk, battlerDef, move) && aiData->hpPercents[battlerAtk] < 40)
if (GetBattleMovePriority(battlerAtk, aiData->abilities[battlerAtk], move) < 0 && AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) && aiData->hpPercents[battlerAtk] < 40)
ADJUST_SCORE(-2); // don't want to move last
break;
default:
@ -1759,7 +1759,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
&& (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)
&& !PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))
{
if (AI_IsFaster(battlerAtk, battlerDef, move)) // Attacker should go first
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Attacker should go first
{
if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF)
ADJUST_SCORE(-10); // no anticipated move to disable
@ -1781,7 +1781,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
&& (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)
&& !DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
{
if (AI_IsFaster(battlerAtk, battlerDef, move)) // Attacker should go first
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Attacker should go first
{
if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF)
ADJUST_SCORE(-10); // no anticipated move to encore
@ -2163,7 +2163,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break;
case EFFECT_SPITE:
case EFFECT_MIMIC:
if (AI_IsFaster(battlerAtk, battlerDef, move)) // Attacker should go first
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Attacker should go first
{
if (gLastMoves[battlerDef] == MOVE_NONE
|| gLastMoves[battlerDef] == 0xFFFF)
@ -2320,7 +2320,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (isDoubleBattle)
{
if (IsHazardMove(aiData->partnerMove) // partner is going to set up hazards
&& AI_IsFaster(BATTLE_PARTNER(battlerAtk), battlerAtk, aiData->partnerMove)) // partner is going to set up before the potential Defog
&& AI_IsFaster(BATTLE_PARTNER(battlerAtk), battlerAtk, aiData->partnerMove, predictedMove, CONSIDER_PRIORITY)) // partner is going to set up before the potential Defog
{
ADJUST_SCORE(-10);
break; // Don't use Defog if partner is going to set up hazards
@ -2348,7 +2348,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break;
case EFFECT_SEMI_INVULNERABLE:
if (predictedMove != MOVE_NONE
&& AI_IsSlower(battlerAtk, battlerDef, move)
&& AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)
&& GetMoveEffect(predictedMove) == EFFECT_SEMI_INVULNERABLE)
ADJUST_SCORE(-10); // Don't Fly/dig/etc if opponent is going to fly/dig/etc after you
@ -2495,7 +2495,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_ME_FIRST:
if (predictedMove != MOVE_NONE)
{
if (AI_IsSlower(battlerAtk, battlerDef, move))
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
ADJUST_SCORE(-10); // Target is predicted to go first, Me First will fail
else if (GetMoveEffect(predictedMove) != GetMoveEffect(move))
return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score);
@ -2664,7 +2664,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
}
break;
case EFFECT_ELECTRIFY:
if (AI_IsSlower(battlerAtk, battlerDef, move)
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)
//|| GetMoveTypeSpecial(battlerDef, predictedMove) == TYPE_ELECTRIC // Move will already be electric type
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
ADJUST_SCORE(-10);
@ -2696,7 +2696,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_INSTRUCT:
{
u32 instructedMove;
if (AI_IsSlower(battlerAtk, battlerDef, move))
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
instructedMove = predictedMove;
else
instructedMove = gLastMoves[battlerDef];
@ -2733,21 +2733,21 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break;
case EFFECT_QUASH:
if (!isDoubleBattle
|| AI_IsSlower(battlerAtk, battlerDef, move)
|| AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
ADJUST_SCORE(-10);
break;
case EFFECT_AFTER_YOU:
if (!IsTargetingPartner(battlerAtk, battlerDef)
|| !isDoubleBattle
|| AI_IsSlower(battlerAtk, battlerDef, move)
|| AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
ADJUST_SCORE(-10);
break;
case EFFECT_SUCKER_PUNCH:
if ((HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_STATUS) && RandomPercentage(RNG_AI_SUCKER_PUNCH, SUCKER_PUNCH_CHANCE)) // Player has a status move
|| (IsBattleMoveStatus(predictedMove) && RandomPercentage(RNG_AI_SUCKER_PUNCH, SUCKER_PUNCH_PREDICTION_CHANCE) && (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_PREDICT_MOVE)) // AI actively predicting incoming status move
|| AI_IsSlower(battlerAtk, battlerDef, move)) // Opponent going first
|| AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Opponent going first
ADJUST_SCORE(-10);
break;
case EFFECT_TAILWIND:
@ -2782,7 +2782,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10);
break;
case EFFECT_FLAIL:
if (AI_IsSlower(battlerAtk, battlerDef, move) // Opponent should go first
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) // Opponent should go first
|| aiData->hpPercents[battlerAtk] > 50)
ADJUST_SCORE(-4);
break;
@ -2817,7 +2817,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
else if (CanAIFaintTarget(battlerAtk, battlerDef, 0))
ADJUST_SCORE(-10);
else if (CanTargetFaintAi(battlerDef, battlerAtk)
&& AI_IsSlower(battlerAtk, battlerDef, move))
&& AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
ADJUST_SCORE(-10);
break;
case EFFECT_JUNGLE_HEALING:
@ -2848,7 +2848,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
u32 defPrio = GetBattleMovePriority(battlerDef, aiData->abilities[battlerDef], predictedMove);
if (predictedMove == MOVE_NONE
|| IsBattleMoveStatus(predictedMove)
|| AI_IsSlower(battlerAtk, battlerDef, move)
|| AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)
|| defPrio < 1
|| defPrio > 3) // Opponent going first or not using priority move
ADJUST_SCORE(-10);
@ -2924,9 +2924,7 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, AI_ATTACKING)
&& effect != EFFECT_EXPLOSION && effect != EFFECT_MISTY_EXPLOSION)
{
if (CanIndexMoveGuaranteeFaintTarget(battlerAtk, battlerDef, movesetIndex))
ADJUST_SCORE(1); // Bonus point if the KO is guaranteed
if (AI_IsFaster(battlerAtk, battlerDef, move))
if (AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY))
ADJUST_SCORE(FAST_KILL);
else
ADJUST_SCORE(SLOW_KILL);
@ -2935,7 +2933,10 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
&& GetWhichBattlerFasterOrTies(battlerAtk, battlerDef, TRUE) != AI_IS_FASTER
&& GetBattleMovePriority(battlerAtk, gAiLogicData->abilities[battlerAtk], move) > 0)
{
ADJUST_SCORE(LAST_CHANCE);
if (RandomPercentage(RNG_AI_PRIORITIZE_LAST_CHANCE, PRIORITIZE_LAST_CHANCE_CHANCE))
ADJUST_SCORE(SLOW_KILL + 2); // Don't outscore Fast Kill (which gets a bonus point in AI_CompareDamagingMoves), but do outscore Slow Kill getting the same
else
ADJUST_SCORE(LAST_CHANCE);
}
return score;
@ -3009,7 +3010,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
// Adjust for always crit moves
if (MoveAlwaysCrits(aiData->partnerMove) && aiData->abilities[battlerAtk] == ABILITY_ANGER_POINT)
{
if (AI_IsSlower(battlerAtk, battlerAtkPartner, move)) // Partner moving first
if (AI_IsSlower(battlerAtk, battlerAtkPartner, move, predictedMove, CONSIDER_PRIORITY)) // Partner moving first
{
// discourage raising our attack since it's about to be maxed out
if (IsAttackBoostMoveEffect(effect))
@ -3182,7 +3183,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case ABILITY_ANGER_POINT:
if (MoveAlwaysCrits(move)
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)
&& AI_IsFaster(battlerAtk, battlerAtkPartner, move)
&& AI_IsFaster(battlerAtk, battlerAtkPartner, move, predictedMove, CONSIDER_PRIORITY)
&& isFriendlyFireOK)
{
if (MoveAlwaysCrits(move))
@ -3501,7 +3502,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_INSTRUCT:
{
u16 instructedMove;
if (AI_IsFaster(battlerAtk, battlerAtkPartner, move))
if (AI_IsFaster(battlerAtk, battlerAtkPartner, move, predictedMove, CONSIDER_PRIORITY))
instructedMove = aiData->partnerMove;
else
instructedMove = gLastMoves[battlerAtkPartner];
@ -3518,8 +3519,8 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (!(gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && HasMoveWithEffect(battlerAtkPartner, EFFECT_TRICK_ROOM))
ADJUST_SCORE(DECENT_EFFECT);
if (AI_IsSlower(battlerAtkPartner, FOE(battlerAtkPartner), aiData->partnerMove) // Opponent mon 1 goes before partner
&& AI_IsSlower(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)), aiData->partnerMove)) // Opponent mon 2 goes before partner
if (AI_IsSlower(battlerAtkPartner, FOE(battlerAtkPartner), aiData->partnerMove, predictedMove, CONSIDER_PRIORITY) // Opponent mon 1 goes before partner
&& AI_IsSlower(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)), aiData->partnerMove, predictedMove, CONSIDER_PRIORITY)) // Opponent mon 2 goes before partner
{
if (partnerEffect == EFFECT_COUNTER || partnerEffect == EFFECT_MIRROR_COAT)
break; // These moves need to go last
@ -3528,8 +3529,8 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break;
case EFFECT_HEAL_PULSE:
case EFFECT_HIT_ENEMY_HEAL_ALLY:
if (AI_IsFaster(battlerAtk, FOE(battlerAtk), move)
&& AI_IsFaster(battlerAtk, BATTLE_PARTNER(FOE(battlerAtk)), move)
if (AI_IsFaster(battlerAtk, FOE(battlerAtk), move, predictedMove, CONSIDER_PRIORITY)
&& AI_IsFaster(battlerAtk, BATTLE_PARTNER(FOE(battlerAtk)), move, predictedMove, CONSIDER_PRIORITY)
&& gBattleMons[battlerAtkPartner].hp < gBattleMons[battlerAtkPartner].maxHP / 2)
RETURN_SCORE_PLUS(WEAK_EFFECT);
break;
@ -3635,16 +3636,50 @@ static bool32 IsPinchBerryItemEffect(enum ItemHoldEffect holdEffect)
}
}
static s32 CompareMoveAccuracies(u32 battlerAtk, u32 battlerDef, u32 moveSlot1, u32 moveSlot2)
static enum MoveComparisonResult CompareMoveAccuracies(u32 battlerAtk, u32 battlerDef, u32 moveSlot1, u32 moveSlot2)
{
u32 acc1 = gAiLogicData->moveAccuracy[battlerAtk][battlerDef][moveSlot1];
u32 acc2 = gAiLogicData->moveAccuracy[battlerAtk][battlerDef][moveSlot2];
if (acc1 > acc2)
return 1;
return MOVE_WON_COMPARISON;
else if (acc2 > acc1)
return -1;
return 0;
return MOVE_LOST_COMPARISON;
return MOVE_NEUTRAL_COMPARISON;
}
static enum MoveComparisonResult CompareMoveSpeeds(u32 battlerAtk, u32 battlerDef, u16 move1, u16 move2)
{
u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData);
u32 speed1 = AI_WhoStrikesFirst(battlerAtk, battlerDef, move1, predictedMove, CONSIDER_PRIORITY);
u32 speed2 = AI_WhoStrikesFirst(battlerAtk, battlerDef, move2, predictedMove, CONSIDER_PRIORITY);
if (speed1 == AI_IS_FASTER && speed2 == AI_IS_SLOWER)
return MOVE_WON_COMPARISON;
if (speed2 == AI_IS_FASTER && speed1 == AI_IS_SLOWER)
return MOVE_LOST_COMPARISON;
return MOVE_NEUTRAL_COMPARISON;
}
static enum MoveComparisonResult CompareGuaranteeFaintTarget(u32 battlerAtk, u32 battlerDef, u16 moveSlot1, u16 moveSlot2, u16 *moves)
{
s32 dmg1, dmg2;
bool32 guarantee1, guarantee2;
if (!(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT))
return 0;
// Explictly care about guaranteed KOs universally
dmg1 = gAiLogicData->simulatedDmg[battlerAtk][battlerDef][moveSlot1].minimum;
guarantee1 = (gBattleMons[battlerDef].hp <= dmg1 && !CanEndureHit(battlerAtk, battlerDef, moves[moveSlot1]));
dmg2 = gAiLogicData->simulatedDmg[battlerAtk][battlerDef][moveSlot2].minimum;
guarantee2 = (gBattleMons[battlerDef].hp <= dmg2 && !CanEndureHit(battlerAtk, battlerDef, moves[moveSlot2]));
if (guarantee1 && !guarantee2)
return MOVE_WON_COMPARISON;
if (guarantee2 && !guarantee1)
return MOVE_LOST_COMPARISON;
return MOVE_NEUTRAL_COMPARISON;
}
static inline bool32 ShouldUseSpreadDamageMove(u32 battlerAtk, u32 move, u32 moveIndex, u32 hitsToFaintOpposingBattler)
@ -3718,9 +3753,11 @@ static void AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef)
// Priority list:
// 1. Less no of hits to ko
// 2. Not charging
// 3. More accuracy
// 4. Better effect
// 2. Priority if outsped and a OHKO
// 3. Not charging
// 4. More accuracy
// 5. Guaranteed KO
// 6. Better effect
// Current move requires the least hits to KO. Compare with other moves.
if (leastHits == noOfHits[currId])
@ -3738,23 +3775,55 @@ static void AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef)
else if (!isTwoTurnNotSemiInvulnerableMove[i] && isTwoTurnNotSemiInvulnerableMove[currId])
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_NOT_CHARGING);
// Comparing KOs
if (noOfHits[currId] == 1)
{
// Use priority to get fast KO if outsped
switch (CompareMoveSpeeds(battlerAtk, battlerDef, moves[currId], moves[i]))
{
case MOVE_WON_COMPARISON:
tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_SPEED);
break;
case MOVE_LOST_COMPARISON:
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_SPEED);
break;
case MOVE_NEUTRAL_COMPARISON:
break;
}
// Min roll KOs
switch (CompareGuaranteeFaintTarget(battlerAtk, battlerDef, currId, i, moves))
{
case MOVE_WON_COMPARISON:
tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_GUARANTEE);
break;
case MOVE_LOST_COMPARISON:
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_GUARANTEE);
break;
case MOVE_NEUTRAL_COMPARISON:
break;
}
}
switch (CompareMoveAccuracies(battlerAtk, battlerDef, currId, i))
{
case 1:
case MOVE_WON_COMPARISON:
tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_ACCURACY);
break;
case -1:
case MOVE_LOST_COMPARISON:
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_ACCURACY);
break;
case MOVE_NEUTRAL_COMPARISON:
break;
}
switch (AI_WhichMoveBetter(moves[currId], moves[i], battlerAtk, battlerDef, noOfHits[currId]))
{
case 1:
case MOVE_WON_COMPARISON:
tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_EFFECT);
break;
case -1:
case MOVE_LOST_COMPARISON:
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_EFFECT);
break;
case MOVE_NEUTRAL_COMPARISON:
break;
}
}
}
@ -4111,7 +4180,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
ADJUST_SCORE(IncreaseSubstituteMoveScore(battlerAtk, battlerDef, move));
break;
case EFFECT_MIMIC:
if (AI_IsFaster(battlerAtk, battlerDef, move))
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
{
if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF
&& (GetMoveEffect(gLastMoves[battlerDef]) != GetMoveEffect(move)))
@ -4180,7 +4249,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
&& (gLastMoves[battlerDef] != MOVE_NONE)
&& (gLastMoves[battlerDef] != 0xFFFF)
&& (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)
&& (AI_IsFaster(battlerAtk, battlerDef, move)))
&& (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)))
{
if (CanTargetMoveFaintAi(gLastMoves[battlerDef], battlerDef, battlerAtk, 1))
ADJUST_SCORE(GOOD_EFFECT); // Disable move that can kill attacker
@ -4220,7 +4289,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
case EFFECT_DESTINY_BOND:
if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
break;
else if (AI_IsFaster(battlerAtk, battlerDef, move) && CanTargetFaintAi(battlerDef, battlerAtk))
else if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) && CanTargetFaintAi(battlerDef, battlerAtk))
ADJUST_SCORE(GOOD_EFFECT);
break;
case EFFECT_SPITE:
@ -4435,7 +4504,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
if (predictedMove != MOVE_NONE && !isDoubleBattle)
{
enum BattleMoveEffects predictedEffect = GetMoveEffect(predictedMove);
if ((AI_IsFaster(battlerAtk, battlerDef, move))
if ((AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
&& (predictedEffect == EFFECT_EXPLOSION
|| predictedEffect == EFFECT_MISTY_EXPLOSION
|| predictedEffect == EFFECT_PROTECT))
@ -4486,7 +4555,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
break;
case EFFECT_ATTRACT:
if (!isDoubleBattle
&& (AI_IsSlower(battlerAtk, battlerDef, move))
&& (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
&& BattlerWillFaintFromSecondaryDamage(battlerDef, aiData->abilities[battlerDef]))
break; // Don't use if the attract won't have a change to activate
if (gBattleMons[battlerDef].status1 & STATUS1_ANY
@ -4513,7 +4582,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
if (isDoubleBattle)
{
if (IsHazardMove(aiData->partnerMove) // Partner is going to set up hazards
&& AI_IsSlower(battlerAtk, BATTLE_PARTNER(battlerAtk), move)) // Partner going first
&& AI_IsSlower(battlerAtk, BATTLE_PARTNER(battlerAtk), move, predictedMove, CONSIDER_PRIORITY)) // Partner going first
break; // Don't use Defog if partner is going to set up hazards
}
ADJUST_SCORE(IncreaseStatDownScore(battlerAtk, battlerDef, STAT_EVASION));
@ -5007,7 +5076,7 @@ case EFFECT_GUARD_SPLIT:
ADJUST_SCORE(DECENT_EFFECT);
break;
case EFFECT_HEAL_BLOCK:
if (AI_IsFaster(battlerAtk, battlerDef, move) && predictedMove != MOVE_NONE && IsHealingMove(predictedMove))
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) && predictedMove != MOVE_NONE && IsHealingMove(predictedMove))
ADJUST_SCORE(DECENT_EFFECT); // Try to cancel healing move
else if (HasHealingEffect(battlerDef) || aiData->holdEffects[battlerDef] == HOLD_EFFECT_LEFTOVERS
|| (aiData->holdEffects[battlerDef] == HOLD_EFFECT_BLACK_SLUDGE && IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON)))
@ -5041,7 +5110,7 @@ case EFFECT_GUARD_SPLIT:
ADJUST_SCORE(BEST_EFFECT);
break;
case EFFECT_QUASH:
if (isDoubleBattle && AI_IsSlower(BATTLE_PARTNER(battlerAtk), battlerDef, aiData->partnerMove))
if (isDoubleBattle && AI_IsSlower(BATTLE_PARTNER(battlerAtk), battlerDef, aiData->partnerMove, predictedMove, CONSIDER_PRIORITY))
ADJUST_SCORE(DECENT_EFFECT); // Attacker partner wouldn't go before target
break;
case EFFECT_TAILWIND:
@ -5056,7 +5125,7 @@ case EFFECT_GUARD_SPLIT:
if (IsBattlerGrounded(battlerAtk) && HasDamagingMoveOfType(battlerDef, TYPE_ELECTRIC)
&& !(effectiveness == UQ_4_12(0.0))) // Doesn't resist ground move
{
if (AI_IsFaster(battlerAtk, battlerDef, move)) // Attacker goes first
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Attacker goes first
{
if (predictedType == TYPE_GROUND)
ADJUST_SCORE(GOOD_EFFECT); // Cause the enemy's move to fail
@ -5071,7 +5140,7 @@ case EFFECT_GUARD_SPLIT:
}
break;
case EFFECT_CAMOUFLAGE:
if (predictedMove != MOVE_NONE && AI_IsFaster(battlerAtk, battlerDef, move) // Attacker goes first
if (predictedMove != MOVE_NONE && AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) // Attacker goes first
&& !IsBattleMoveStatus(move) && effectiveness != UQ_4_12(0.0))
ADJUST_SCORE(DECENT_EFFECT);
break;
@ -5098,7 +5167,7 @@ case EFFECT_GUARD_SPLIT:
ADJUST_SCORE(DECENT_EFFECT);
break;
case EFFECT_ENDEAVOR:
if (AI_IsSlower(battlerAtk, battlerDef, move) && !CanTargetFaintAi(battlerDef, battlerAtk))
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) && !CanTargetFaintAi(battlerDef, battlerAtk))
ADJUST_SCORE(DECENT_EFFECT);
break;
case EFFECT_REVIVAL_BLESSING:
@ -5377,7 +5446,7 @@ case EFFECT_GUARD_SPLIT:
case MOVE_EFFECT_THROAT_CHOP:
if (IsSoundMove(GetBestDmgMoveFromBattler(battlerDef, battlerAtk, AI_DEFENDING)))
{
if (AI_IsFaster(battlerAtk, battlerDef, move))
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
ADJUST_SCORE(GOOD_EFFECT);
else
ADJUST_SCORE(DECENT_EFFECT);
@ -5433,7 +5502,7 @@ static s32 AI_ForceSetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32
return score;
if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_SMART_SWITCHING
&& AI_IsSlower(battlerAtk, battlerDef, move)
&& AI_IsSlower(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY)
&& CanTargetFaintAi(battlerDef, battlerAtk)
&& GetBattleMovePriority(battlerAtk, gAiLogicData->abilities[battlerAtk], move) == 0)
{
@ -6009,7 +6078,7 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(GOOD_EFFECT);
else if (hitsToKO == 1)
ADJUST_SCORE(BEST_EFFECT);
else if (IsSwitchOutEffect(GetMoveEffect(predictedMove)) && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Pursuit against fast U-Turn
else if (IsSwitchOutEffect(GetMoveEffect(predictedMove)) && AI_WhoStrikesFirst(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) == AI_IS_SLOWER) // Pursuit against fast U-Turn
ADJUST_SCORE(DECENT_EFFECT);
break;
}

View File

@ -171,6 +171,34 @@ static bool32 AI_DoesChoiceEffectBlockMove(u32 battler, u32 move)
return FALSE;
}
static inline bool32 CanBattlerWin1v1(u32 hitsToKOAI, u32 hitsToKOPlayer, bool32 isBattlerFirst)
{
// Player's best move deals 0 damage
if (hitsToKOAI == 0 && hitsToKOPlayer > 0)
return TRUE;
// AI's best move deals 0 damage
if (hitsToKOPlayer == 0 && hitsToKOAI > 0)
return FALSE;
// Neither mon can damage the other
if (hitsToKOPlayer == 0 && hitsToKOAI == 0)
return FALSE;
// Different KO thresholds depending on who goes first
if (isBattlerFirst)
{
if (hitsToKOAI >= hitsToKOPlayer)
return TRUE;
}
else
{
if (hitsToKOAI > hitsToKOPlayer)
return TRUE;
}
return FALSE;
}
// Note that as many return statements as possible are INTENTIONALLY put after all of the loops;
// the function can take a max of about 0.06s to run, and this prevents the player from identifying
// whether the mon will switch or not by seeing how long the delay is before they select a move
@ -178,12 +206,13 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler)
{
//Variable initialization
u8 opposingPosition;
s32 i, damageDealt = 0, maxDamageDealt = 0, damageTaken = 0, maxDamageTaken = 0;
u32 aiMove, playerMove, aiBestMove = MOVE_NONE, aiAbility = gAiLogicData->abilities[battler], opposingBattler;
s32 i, damageDealt = 0, maxDamageDealt = 0, damageTaken = 0, maxDamageTaken = 0, maxDamageTakenPriority = 0;
u32 aiMove, playerMove, bestPlayerPriorityMove = MOVE_NONE, aiAbility = gAiLogicData->abilities[battler], opposingBattler;
bool32 getsOneShot = FALSE, hasStatusMove = FALSE, hasSuperEffectiveMove = FALSE;
u32 typeMatchup;
enum BattleMoveEffects aiMoveEffect;
u32 hitsToKoPlayer = 0, hitsToKoAI = 0;
u32 hitsToKoAI = 0, hitsToKoAIPriority = 0, hitsToKoPlayer = 0;
bool32 canBattlerWin1v1 = FALSE, isBattlerFirst, isBattlerFirstPriority;
// Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer
if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING))
@ -197,6 +226,28 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler)
opposingBattler = GetBattlerAtPosition(opposingPosition);
u16 *playerMoves = GetMovesArray(opposingBattler);
// Get max damage mon could take
for (i = 0; i < MAX_MON_MOVES; i++)
{
playerMove = SMART_SWITCHING_OMNISCIENT ? gBattleMons[opposingBattler].moves[i] : playerMoves[i];
if (playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH)
{
damageTaken = AI_GetDamage(opposingBattler, battler, i, AI_DEFENDING, gAiLogicData);
if (damageTaken > maxDamageTaken && !AI_DoesChoiceEffectBlockMove(opposingBattler, playerMove))
{
maxDamageTaken = damageTaken;
}
if (GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[battler], playerMove) > 0 && damageTaken > maxDamageTakenPriority && !AI_DoesChoiceEffectBlockMove(opposingBattler, playerMove))
{
maxDamageTakenPriority = damageTaken;
bestPlayerPriorityMove = playerMove;
}
}
}
hitsToKoAI = GetNoOfHitsToKOBattlerDmg(maxDamageTaken, battler);
hitsToKoAIPriority = GetNoOfHitsToKOBattlerDmg(maxDamageTakenPriority, battler);
for (i = 0; i < MAX_MON_MOVES; i++)
{
aiMove = gBattleMons[battler].moves[i];
@ -220,53 +271,37 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler)
}
// Only check damage if it's a damaging move
if (!IsBattleMoveStatus(aiMove))
if (!IsBattleMoveStatus(aiMove) && !AI_DoesChoiceEffectBlockMove(battler, aiMove))
{
// Check if mon has a super effective move
if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) >= UQ_4_12(2.0) && !AI_DoesChoiceEffectBlockMove(battler, aiMove))
if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) >= UQ_4_12(2.0))
hasSuperEffectiveMove = TRUE;
// Get maximum damage mon can deal
damageDealt = AI_GetDamage(battler, opposingBattler, i, AI_ATTACKING, gAiLogicData);
if (damageDealt > maxDamageDealt && !AI_DoesChoiceEffectBlockMove(battler, aiMove))
if (!canBattlerWin1v1 ) // Once we can win a 1v1 we don't need to track this, but want to run the rest of the function to keep the runtime the same regardless of when we find the winning move
{
maxDamageDealt = damageDealt;
aiBestMove = aiMove;
hitsToKoPlayer = GetNoOfHitsToKOBattlerDmg(damageDealt, opposingBattler);
isBattlerFirst = AI_IsFaster(battler, opposingBattler, aiMove, GetIncomingMove(battler, opposingBattler, gAiLogicData), CONSIDER_PRIORITY);
isBattlerFirstPriority = AI_IsFaster(battler, opposingBattler, aiMove, bestPlayerPriorityMove, CONSIDER_PRIORITY);
canBattlerWin1v1 = CanBattlerWin1v1(hitsToKoAI, hitsToKoPlayer, isBattlerFirst) && CanBattlerWin1v1(hitsToKoAIPriority, hitsToKoPlayer, isBattlerFirstPriority);
}
}
}
}
hitsToKoPlayer = GetNoOfHitsToKOBattlerDmg(maxDamageDealt, opposingBattler);
// Calculate type advantage
typeMatchup = GetBattleMonTypeMatchup(gBattleMons[opposingBattler], gBattleMons[battler]);
// Get max damage mon could take
for (i = 0; i < MAX_MON_MOVES; i++)
{
playerMove = SMART_SWITCHING_OMNISCIENT ? gBattleMons[opposingBattler].moves[i] : playerMoves[i];
if (playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH)
{
damageTaken = AI_GetDamage(opposingBattler, battler, i, AI_DEFENDING, gAiLogicData);
if (damageTaken > maxDamageTaken && !AI_DoesChoiceEffectBlockMove(opposingBattler, playerMove))
{
maxDamageTaken = damageTaken;
}
}
}
hitsToKoAI = GetNoOfHitsToKOBattlerDmg(maxDamageTaken, battler);
// Check if mon gets one shot
if (maxDamageTaken > gBattleMons[battler].hp
&& !(gItemsInfo[gBattleMons[battler].item].holdEffect == HOLD_EFFECT_FOCUS_SASH || (!IsMoldBreakerTypeAbility(opposingBattler, gBattleMons[opposingBattler].ability) && B_STURDY >= GEN_5 && aiAbility == ABILITY_STURDY)))
&& !(gItemsInfo[gBattleMons[battler].item].holdEffect == HOLD_EFFECT_FOCUS_SASH || (!IsMoldBreakerTypeAbility(opposingBattler, gAiLogicData->abilities[opposingBattler]) && B_STURDY >= GEN_5 && aiAbility == ABILITY_STURDY)))
{
getsOneShot = TRUE;
}
// Check if current mon can 1v1 in spite of bad matchup, and don't switch out if it can
if ((hitsToKoPlayer != 0 && (hitsToKoPlayer < hitsToKoAI || hitsToKoAI == 0)) || (hitsToKoPlayer == hitsToKoAI && AI_IsFaster(battler, opposingBattler, aiBestMove)))
if (canBattlerWin1v1)
return FALSE;
// If we don't have any other viable options, don't switch out
@ -384,8 +419,8 @@ static bool32 ShouldSwitchIfAllMovesBad(u32 battler)
{
aiMove = gBattleMons[battler].moves[moveIndex];
if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) > UQ_4_12(0.0) && aiMove != MOVE_NONE
&& !CanAbilityAbsorbMove(battler, opposingBattler, gBattleMons[opposingBattler].ability, aiMove, GetBattleMoveType(aiMove), AI_CHECK)
&& !CanAbilityBlockMove(battler, opposingBattler, gBattleMons[battler].ability, gBattleMons[opposingBattler].ability, aiMove, AI_CHECK)
&& !CanAbilityAbsorbMove(battler, opposingBattler, gAiLogicData->abilities[opposingBattler], aiMove, GetBattleMoveType(aiMove), AI_CHECK)
&& !CanAbilityBlockMove(battler, opposingBattler, gBattleMons[battler].ability, gAiLogicData->abilities[opposingBattler], aiMove, AI_CHECK)
&& (!ALL_MOVES_BAD_STATUS_MOVES_BAD || gMovesInfo[aiMove].power != 0)) // If using ALL_MOVES_BAD_STATUS_MOVES_BAD, then need power to be non-zero
return FALSE;
}
@ -1788,7 +1823,7 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, u32 battler)
u16 maxHP = gAiLogicData->switchinCandidate.battleMon.maxHP, item = gAiLogicData->switchinCandidate.battleMon.item, heldItemEffect = GetItemHoldEffect(item);
u8 weatherDuration = gWishFutureKnock.weatherDuration, holdEffectParam = GetItemHoldEffectParam(item);
u32 opposingBattler = GetOppositeBattler(battler);
u32 opposingAbility = gBattleMons[opposingBattler].ability, ability = gAiLogicData->switchinCandidate.battleMon.ability;
u32 opposingAbility = gAiLogicData->abilities[opposingBattler], ability = gAiLogicData->switchinCandidate.battleMon.ability;
bool32 usedSingleUseHealingItem = FALSE, opponentCanBreakMold = IsMoldBreakerTypeAbility(opposingBattler, opposingAbility);
s32 currentHP = startingHP;
@ -1939,7 +1974,7 @@ static int GetRandomSwitchinWithBatonPass(int aliveCount, int bits, int firstId,
return PARTY_SIZE;
}
static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattler, struct BattlePokemon battleMon)
static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattler, struct BattlePokemon battleMon, u32 *bestPlayerMove)
{
int i = 0;
u32 playerMove;
@ -1955,7 +1990,35 @@ static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattle
if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move
return damageTaken;
if (damageTaken > maxDamageTaken)
{
maxDamageTaken = damageTaken;
*bestPlayerMove = playerMove;
}
}
}
return maxDamageTaken;
}
static s32 GetMaxPriorityDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattler, struct BattlePokemon battleMon, u32 *bestPlayerPriorityMove)
{
int i = 0;
u32 playerMove;
u16 *playerMoves = GetMovesArray(opposingBattler);
s32 damageTaken = 0, maxDamageTaken = 0;
for (i = 0; i < MAX_MON_MOVES; i++)
{
playerMove = SMART_SWITCHING_OMNISCIENT ? gBattleMons[opposingBattler].moves[i] : playerMoves[i];
if (GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], playerMove) > 0 && playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH)
{
damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, AI_DEFENDING);
if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move
return damageTaken;
if (damageTaken > maxDamageTaken)
{
maxDamageTaken = damageTaken;
*bestPlayerPriorityMove = playerMove;
}
}
}
return maxDamageTaken;
@ -2044,9 +2107,9 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
int i, j, aliveCount = 0, bits = 0, aceMonCount = 0;
s32 defensiveMonHitKOThreshold = 3; // 3HKO threshold that candidate defensive mons must exceed
s32 playerMonHP = gBattleMons[opposingBattler].hp, maxDamageDealt = 0, damageDealt = 0;
u32 aiMove, hitsToKOAI, maxHitsToKO = 0;
u32 aiMove, hitsToKOAI, hitsToKOPlayer, hitsToKOAIPriority, bestPlayerMove = MOVE_NONE, bestPlayerPriorityMove = MOVE_NONE, maxHitsToKO = 0;
u32 bestResist = UQ_4_12(2.0), bestResistEffective = UQ_4_12(2.0), typeMatchup; // 2.0 is the default "Neutral" matchup from GetBattleMonTypeMatchup
bool32 isFreeSwitch = IsFreeSwitch(switchType, battlerIn1, opposingBattler), isSwitchinFirst, canSwitchinWin1v1;
bool32 isFreeSwitch = IsFreeSwitch(switchType, battlerIn1, opposingBattler), isSwitchinFirst, isSwitchinFirstPriority, canSwitchinWin1v1;
// Iterate through mons
for (i = firstId; i < lastId; i++)
@ -2077,7 +2140,8 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
continue;
// Get max number of hits for player to KO AI mon and type matchup for defensive switching
hitsToKOAI = GetSwitchinHitsToKO(GetMaxDamagePlayerCouldDealToSwitchin(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon), battler);
hitsToKOAI = GetSwitchinHitsToKO(GetMaxDamagePlayerCouldDealToSwitchin(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, &bestPlayerMove), battler);
hitsToKOAIPriority = GetSwitchinHitsToKO(GetMaxPriorityDamagePlayerCouldDealToSwitchin(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, &bestPlayerPriorityMove), battler);
typeMatchup = GetBattleMonTypeMatchup(gBattleMons[opposingBattler], gAiLogicData->switchinCandidate.battleMon);
// Check through current mon's moves
@ -2085,10 +2149,12 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
{
aiMove = gAiLogicData->switchinCandidate.battleMon.moves[j];
damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, AI_ATTACKING);
hitsToKOPlayer = GetNoOfHitsToKOBattlerDmg(damageDealt, opposingBattler);
// Offensive switchin decisions are based on which whether switchin moves first and whether it can win a 1v1
isSwitchinFirst = AI_WhoStrikesFirstPartyMon(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, aiMove);
canSwitchinWin1v1 = CanSwitchinWin1v1(hitsToKOAI, GetNoOfHitsToKOBattlerDmg(damageDealt, opposingBattler), isSwitchinFirst, isFreeSwitch);
isSwitchinFirst = AI_IsPartyMonFaster(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, aiMove, bestPlayerMove, CONSIDER_PRIORITY);
isSwitchinFirstPriority = AI_IsPartyMonFaster(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, aiMove, bestPlayerPriorityMove, CONSIDER_PRIORITY);
canSwitchinWin1v1 = CanSwitchinWin1v1(hitsToKOAI, hitsToKOPlayer, isSwitchinFirst, isFreeSwitch) && CanSwitchinWin1v1(hitsToKOAIPriority, hitsToKOPlayer, isSwitchinFirstPriority, isFreeSwitch); // AI must successfully 1v1 with and without priority to be considered a good option
// Check for Baton Pass; hitsToKO requirements mean mon can boost and BP without dying whether it's slower or not
if (GetMoveEffect(aiMove) == EFFECT_BATON_PASS)
@ -2346,7 +2412,7 @@ static bool32 AiExpectsToFaintPlayer(u32 battler)
if (!IsBattlerAlly(target, battler)
&& CanIndexMoveFaintTarget(battler, target, gAiBattleData->chosenMoveIndex[battler], AI_ATTACKING)
&& AI_IsFaster(battler, target, GetAIChosenMove(battler)))
&& AI_IsFaster(battler, target, GetAIChosenMove(battler), GetIncomingMove(battler, target, gAiLogicData), CONSIDER_PRIORITY))
{
// We expect to faint the target and move first -> dont use an item
return TRUE;

View File

@ -76,14 +76,24 @@ u32 AI_GetDamage(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum DamageCalcC
}
}
bool32 AI_IsFaster(u32 battlerAi, u32 battlerDef, u32 move)
bool32 AI_IsFaster(u32 battlerAi, u32 battlerDef, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority)
{
return (AI_WhoStrikesFirst(battlerAi, battlerDef, move) == AI_IS_FASTER);
return (AI_WhoStrikesFirst(battlerAi, battlerDef, aiMove, playerMove, considerPriority) == AI_IS_FASTER);
}
bool32 AI_IsSlower(u32 battlerAi, u32 battlerDef, u32 move)
bool32 AI_IsSlower(u32 battlerAi, u32 battlerDef, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority)
{
return (AI_WhoStrikesFirst(battlerAi, battlerDef, move) == AI_IS_SLOWER);
return (AI_WhoStrikesFirst(battlerAi, battlerDef, aiMove, playerMove, considerPriority) == AI_IS_SLOWER);
}
bool32 AI_IsPartyMonFaster(u32 battlerAi, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority)
{
return (AI_WhoStrikesFirstPartyMon(battlerAi, battlerDef, switchinCandidate, aiMove, playerMove, considerPriority) == AI_IS_FASTER);
}
bool32 AI_IsPartyMonSlower(u32 battlerAi, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority)
{
return (AI_WhoStrikesFirstPartyMon(battlerAi, battlerDef, switchinCandidate, aiMove, playerMove, considerPriority) == AI_IS_SLOWER);
}
u32 GetAIChosenMove(u32 battlerId)
@ -484,7 +494,7 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler)
enum BattleMoveEffects effect = GetMoveEffect(move);
if (effect == EFFECT_PROTECT && move != MOVE_ENDURE)
return TRUE;
if (effect == EFFECT_SEMI_INVULNERABLE && AI_IsSlower(battlerAI, opposingBattler, GetAIChosenMove(battlerAI)))
if (effect == EFFECT_SEMI_INVULNERABLE && AI_IsSlower(battlerAI, opposingBattler, GetAIChosenMove(battlerAI), GetIncomingMove(battlerAI, opposingBattler, gAiLogicData), CONSIDER_PRIORITY))
return TRUE;
}
return FALSE;
@ -1164,7 +1174,7 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s
}
// Checks if one of the moves has side effects or perks, assuming equal dmg or equal no of hits to KO
s32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo)
enum MoveComparisonResult AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo)
{
bool32 effect1, effect2;
u32 defAbility = gAiLogicData->abilities[battlerDef];
@ -1178,27 +1188,27 @@ s32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32
bool32 moveContact1 = MoveMakesContact(move1);
bool32 moveContact2 = MoveMakesContact(move2);
if (moveContact1 && !moveContact2)
return -1;
return MOVE_LOST_COMPARISON;
if (moveContact2 && !moveContact1)
return 1;
return MOVE_WON_COMPARISON;
}
// Check additional effects.
effect1 = AI_IsMoveEffectInMinus(battlerAtk, battlerDef, move1, noOfHitsToKo);
effect2 = AI_IsMoveEffectInMinus(battlerAtk, battlerDef, move2, noOfHitsToKo);
if (effect2 && !effect1)
return 1;
return MOVE_WON_COMPARISON;
if (effect1 && !effect2)
return -1;
return MOVE_LOST_COMPARISON;
effect1 = AI_IsMoveEffectInPlus(battlerAtk, battlerDef, move1, noOfHitsToKo);
effect2 = AI_IsMoveEffectInPlus(battlerAtk, battlerDef, move2, noOfHitsToKo);
if (effect2 && !effect1)
return -1;
return MOVE_LOST_COMPARISON;
if (effect1 && !effect2)
return 1;
return MOVE_WON_COMPARISON;
return 0;
return MOVE_NEUTRAL_COMPARISON;
}
u32 GetNoOfHitsToKO(u32 dmg, s32 hp)
@ -1261,7 +1271,7 @@ uq4_12_t AI_GetMoveEffectiveness(u32 move, u32 battlerAtk, u32 battlerDef)
* AI_IS_FASTER: is user(ai) faster
* AI_IS_SLOWER: is target faster
*/
s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 moveConsidered)
s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 aiMoveConsidered, u32 playerMoveConsidered, enum ConsiderPriority considerPriority)
{
u32 speedBattlerAI, speedBattler;
enum ItemHoldEffect holdEffectAI = gAiLogicData->holdEffects[battlerAI];
@ -1269,15 +1279,16 @@ s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 moveConsidered)
u32 abilityAI = gAiLogicData->abilities[battlerAI];
u32 abilityPlayer = gAiLogicData->abilities[battler];
u32 predictedMove = GetIncomingMove(battlerAI, battler, gAiLogicData);
if (considerPriority == CONSIDER_PRIORITY)
{
s8 aiPriority = GetBattleMovePriority(battlerAI, abilityAI, aiMoveConsidered);
s8 playerPriority = GetBattleMovePriority(battler, abilityPlayer, playerMoveConsidered);
s8 aiPriority = GetBattleMovePriority(battlerAI, abilityAI, moveConsidered);
s8 playerPriority = GetBattleMovePriority(battler, abilityPlayer, predictedMove);
if (aiPriority > playerPriority)
return AI_IS_FASTER;
else if (aiPriority < playerPriority)
return AI_IS_SLOWER;
if (aiPriority > playerPriority)
return AI_IS_FASTER;
else if (aiPriority < playerPriority)
return AI_IS_SLOWER;
}
speedBattlerAI = GetBattlerTotalSpeedStatArgs(battlerAI, abilityAI, holdEffectAI);
speedBattler = GetBattlerTotalSpeedStatArgs(battler, abilityPlayer, holdEffectPlayer);
@ -1314,7 +1325,7 @@ s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 moveConsidered)
return AI_IS_SLOWER;
}
static bool32 CanEndureHit(u32 battler, u32 battlerTarget, u32 move)
bool32 CanEndureHit(u32 battler, u32 battlerTarget, u32 move)
{
enum BattleMoveEffects effect = GetMoveEffect(move);
if (!AI_BattlerAtMaxHp(battlerTarget) || effect == EFFECT_MULTI_HIT)
@ -2043,7 +2054,7 @@ bool32 CanLowerStat(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 stat)
if (stat == STAT_SPEED)
{
// If AI is faster and doesn't have any mons left, lowering speed doesn't give any
return !(AI_IsFaster(battlerAtk, battlerDef, gAiThinkingStruct->moveConsidered)
return !(AI_IsFaster(battlerAtk, battlerDef, gAiThinkingStruct->moveConsidered, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), DONT_CONSIDER_PRIORITY)
&& CountUsablePartyMons(battlerAtk) == 0
&& !HasMoveWithEffect(battlerAtk, EFFECT_ELECTRO_BALL));
}
@ -2085,7 +2096,7 @@ u32 IncreaseStatDownScore(u32 battlerAtk, u32 battlerDef, u32 stat)
tempScore += DECENT_EFFECT;
break;
case STAT_SPEED:
if (AI_IsSlower(battlerAtk, battlerDef, gAiThinkingStruct->moveConsidered))
if (AI_IsSlower(battlerAtk, battlerDef, gAiThinkingStruct->moveConsidered, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), DONT_CONSIDER_PRIORITY))
tempScore += DECENT_EFFECT;
break;
case STAT_SPATK:
@ -2191,18 +2202,6 @@ bool32 CanIndexMoveFaintTarget(u32 battlerAtk, u32 battlerDef, u32 moveIndex, en
return FALSE;
}
bool32 CanIndexMoveGuaranteeFaintTarget(u32 battlerAtk, u32 battlerDef, u32 moveIndex)
{
s32 dmg;
u16 *moves = gBattleMons[battlerAtk].moves;
dmg = gAiLogicData->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;
}
u16 *GetMovesArray(u32 battler)
{
if (IsAiBattlerAware(battler) || IsAiBattlerAware(BATTLE_PARTNER(battler)))
@ -3127,7 +3126,7 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 mov
if (IsBattlerPredictedToSwitch(battlerDef))
return SHOULD_PIVOT; // Try pivoting so you can switch to a better matchup to counter your new opponent
if (AI_IsFaster(battlerAtk, battlerDef, move)) // Attacker goes first
if (AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY)) // Attacker goes first
{
if (!CanAIFaintTarget(battlerAtk, battlerDef, 0)) // Can't KO foe otherwise
{
@ -3512,7 +3511,7 @@ u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbi
if (((!IsMoldBreakerTypeAbility(battlerAtk, gAiLogicData->abilities[battlerAtk]) && (defAbility == ABILITY_SHIELD_DUST || defAbility == ABILITY_INNER_FOCUS))
|| gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK
|| DoesSubstituteBlockMove(battlerAtk, battlerDef, move)
|| AI_IsSlower(battlerAtk, battlerDef, move))) // Opponent goes first
|| AI_IsSlower(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY))) // Opponent goes first
{
return 0;
}
@ -3520,7 +3519,7 @@ u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbi
|| gBattleMons[battlerDef].status1 & STATUS1_PARALYSIS
|| gBattleMons[battlerDef].volatiles.infatuation
|| gBattleMons[battlerDef].volatiles.confusionTurns > 0)
|| ((AI_IsFaster(battlerAtk, battlerDef, move)) && CanTargetFaintAi(battlerDef, battlerAtk)))
|| ((AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY)) && CanTargetFaintAi(battlerDef, battlerAtk)))
{
return 2; // good idea to flinch
}
@ -3666,7 +3665,7 @@ bool32 ShouldUseRecoilMove(u32 battlerAtk, u32 battlerDef, u32 recoilDmg, u32 mo
bool32 ShouldAbsorb(u32 battlerAtk, u32 battlerDef, u32 move, s32 damage)
{
if (move == 0xFFFF || AI_IsFaster(battlerAtk, battlerDef, move))
if (move == 0xFFFF || AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY))
{
// using item or user goes first
s32 healDmg = (GetMoveAbsorbPercentage(move) * damage) / 100;
@ -3698,7 +3697,7 @@ bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent)
healAmount = maxHP;
if (gStatuses3[battlerAtk] & STATUS3_HEAL_BLOCK)
healAmount = 0;
if (AI_IsFaster(battlerAtk, battlerDef, move))
if (AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY))
{
if (CanTargetFaintAi(battlerDef, battlerAtk)
&& !CanTargetFaintAiWithMod(battlerDef, battlerAtk, healAmount, 0))
@ -4040,17 +4039,17 @@ s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct Battl
return dmg.median;
}
u32 AI_WhoStrikesFirstPartyMon(u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 moveConsidered)
u32 AI_WhoStrikesFirstPartyMon(u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 aiMoveConsidered, u32 playerMoveConsidered, enum ConsiderPriority considerPriority)
{
struct BattlePokemon *savedBattleMons = AllocSaveBattleMons();
gBattleMons[battlerAtk] = switchinCandidate;
SetBattlerAiData(battlerAtk, gAiLogicData);
u32 aiMonFaster = AI_IsFaster(battlerAtk, battlerDef, moveConsidered);
u32 aiWhoStrikesFirst = AI_WhoStrikesFirst(battlerAtk, battlerDef, aiMoveConsidered, playerMoveConsidered, considerPriority);
FreeRestoreBattleMons(savedBattleMons);
SetBattlerAiData(battlerAtk, gAiLogicData);
return aiMonFaster;
return aiWhoStrikesFirst;
}
s32 CountUsablePartyMons(u32 battlerId)
@ -4291,7 +4290,7 @@ static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef,
{
enum AIScore tempScore = NO_INCREASE;
u32 noOfHitsToFaint = NoOfHitsForTargetToFaintBattler(battlerDef, battlerAtk);
u32 aiIsFaster = AI_IsFaster(battlerAtk, battlerDef, TRUE);
u32 aiIsFaster = AI_IsFaster(battlerAtk, battlerDef, MOVE_NONE, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), DONT_CONSIDER_PRIORITY); // Don't care about the priority of our setup move, care about outspeeding otherwise
u32 shouldSetUp = ((noOfHitsToFaint >= 2 && aiIsFaster) || (noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == UNKNOWN_NO_OF_HITS);
u32 i;
u32 statId = GetStatBeingChanged(statChange);
@ -4841,7 +4840,7 @@ enum AIConsiderGimmick ShouldTeraFromCalcs(u32 battler, u32 opposingBattler, str
else
{
// will we go first?
if (AI_WhoStrikesFirst(battler, opposingBattler, killingMove) == AI_IS_FASTER && GetBattleMovePriority(battler, gAiLogicData->abilities[battler], killingMove) >= GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], hardPunishingMove))
if (AI_WhoStrikesFirst(battler, opposingBattler, killingMove, GetIncomingMove(battler, opposingBattler, gAiLogicData), CONSIDER_PRIORITY) == AI_IS_FASTER && GetBattleMovePriority(battler, gAiLogicData->abilities[battler], killingMove) >= GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], hardPunishingMove))
return USE_GIMMICK;
}
}
@ -4954,7 +4953,7 @@ void IncreaseTidyUpScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score)
if (AreAnyHazardsOnSide(GetBattlerSide(battlerDef)) && CountUsablePartyMons(battlerDef) != 0)
ADJUST_SCORE_PTR(-2);
if (gBattleMons[battlerAtk].volatiles.substitute && AI_IsFaster(battlerAtk, battlerDef, move))
if (gBattleMons[battlerAtk].volatiles.substitute && AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), DONT_CONSIDER_PRIORITY))
ADJUST_SCORE_PTR(-10);
if (gBattleMons[battlerDef].volatiles.substitute)
ADJUST_SCORE_PTR(GOOD_EFFECT);
@ -4970,6 +4969,8 @@ bool32 AI_ShouldSpicyExtract(u32 battlerAtk, u32 battlerAtkPartner, u32 move, st
u32 preventsStatLoss;
u32 partnerAbility;
u32 partnerHoldEffect = aiData->holdEffects[battlerAtkPartner];
u32 opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battlerAtk));
u32 opposingBattler = GetBattlerAtPosition(opposingPosition);
if (DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move))
partnerAbility = ABILITY_NONE;
@ -5002,7 +5003,7 @@ bool32 AI_ShouldSpicyExtract(u32 battlerAtk, u32 battlerAtkPartner, u32 move, st
}
return (preventsStatLoss
&& AI_IsFaster(battlerAtk, battlerAtkPartner, TRUE)
&& AI_IsFaster(battlerAtk, battlerAtkPartner, MOVE_NONE, GetIncomingMove(battlerAtk, opposingBattler, gAiLogicData), CONSIDER_PRIORITY)
&& HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL));
}

View File

@ -1010,3 +1010,15 @@ AI_SINGLE_BATTLE_TEST("AI will use recovery move if is in no immediate danger be
TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_RECOVER); }
}
}
AI_SINGLE_BATTLE_TEST("AI has a chance to prioritize last chance priority damage over slow KO")
{
PASSES_RANDOMLY(PRIORITIZE_LAST_CHANCE_CHANCE, 100, RNG_AI_PRIORITIZE_LAST_CHANCE);
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_CAMERUPT) { Speed(2); Moves(MOVE_FLAMETHROWER, MOVE_CELEBRATE); }
OPPONENT(SPECIES_FLOATZEL) { Level(90); Speed(1); HP(1); Moves(MOVE_WAVE_CRASH, MOVE_AQUA_JET); }
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_AQUA_JET); }
}
}

View File

@ -1164,6 +1164,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if all moves
ASSUME(GetMoveType(MOVE_RETURN) == TYPE_NORMAL);
ASSUME(GetMoveType(MOVE_DRAIN_PUNCH) == TYPE_FIGHTING);
ASSUME(gSpeciesInfo[SPECIES_MAROWAK_ALOLA].types[1] == TYPE_GHOST);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_MAROWAK_ALOLA) { Ability(ABILITY_LIGHTNING_ROD); Moves(MOVE_SHADOW_BONE); }
OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_FAKE_OUT, MOVE_RETURN, MOVE_DRAIN_PUNCH, MOVE_THUNDER_PUNCH); Ability(ABILITY_LIMBER); }
OPPONENT(SPECIES_CHANDELURE) { Moves(MOVE_SHADOW_BALL); }
@ -1299,3 +1300,42 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will properly consider immu
TURN { MOVE(player, MOVE_KARATE_CHOP); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 2); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI won't switch out due to bad odds if it can OHKO with a priority move")
{
PASSES_RANDOMLY(100, 100, RNG_AI_SWITCH_HASBADODDS);
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
OPPONENT(SPECIES_CETODDLE) { Level(14); HP(30); Speed(1); Moves(MOVE_ICE_FANG, MOVE_ROCK_SMASH, MOVE_BULLDOZE, MOVE_ICE_SHARD); }
OPPONENT(SPECIES_SPHEAL) { Level(14); Speed(1); Ability(ABILITY_THICK_FAT); Moves(MOVE_ICY_WIND, MOVE_BRINE, MOVE_HIDDEN_POWER, MOVE_SIGNAL_BEAM); }
PLAYER(SPECIES_LITTEN) { Level(15); HP(1); Speed(2); Ability(ABILITY_BLAZE); Moves(MOVE_FIRE_FANG, MOVE_EMBER, MOVE_LICK, MOVE_FAKE_OUT); }
} WHEN {
TURN { MOVE(player, MOVE_FIRE_FANG); EXPECT_MOVE(opponent, MOVE_ICE_SHARD); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will consider player's priority when evaluating switchin candidates")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); HP(1); Moves(MOVE_HEADBUTT); }
OPPONENT(SPECIES_ANNIHILAPE) { Speed(5); Moves(MOVE_DRAIN_PUNCH); }
OPPONENT(SPECIES_GENGAR) { Speed(10); Moves(MOVE_FOCUS_BLAST); }
PLAYER(SPECIES_KINGAMBIT) { Speed(2); Moves(MOVE_SUCKER_PUNCH, MOVE_KNOCK_OFF); }
} WHEN {
TURN { MOVE(player, MOVE_KNOCK_OFF); EXPECT_MOVE(opponent, MOVE_HEADBUTT); EXPECT_SEND_OUT(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will consider player's priority when evaluating Bad Odds 1v1")
{
PASSES_RANDOMLY(SHOULD_SWITCH_HASBADODDS_PERCENTAGE, 100, RNG_AI_SWITCH_HASBADODDS);
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
OPPONENT(SPECIES_GENGAR) { Speed(10); Moves(MOVE_FOCUS_BLAST); }
OPPONENT(SPECIES_SCRAFTY) { Speed(5); Moves(MOVE_DRAIN_PUNCH); }
PLAYER(SPECIES_KINGAMBIT) { Speed(2); Moves(MOVE_SUCKER_PUNCH, MOVE_KNOCK_OFF); }
} WHEN {
TURN { MOVE(player, MOVE_KNOCK_OFF); EXPECT_SWITCH(opponent, 1); }
}
}