Switch AI sees stat, volatile, status, and HP changes on switchin in calcs (#9124)
Some checks are pending
CI / build (push) Waiting to run
CI / docs_validate (push) Waiting to run
CI / allcontributors (push) Waiting to run

This commit is contained in:
Pawkkie 2026-03-18 11:10:03 -04:00 committed by GitHub
parent 070f31e384
commit 2c965d02b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 368 additions and 88 deletions

View File

@ -322,7 +322,6 @@ bool32 IsBattlerPredictedToSwitch(enum BattlerId battler);
enum Move GetIncomingMove(enum BattlerId battler, enum BattlerId opposingBattler, struct AiLogicData *aiData);
enum Move GetIncomingMoveSpeedCheck(enum BattlerId battler, enum BattlerId opposingBattler, struct AiLogicData *aiData);
bool32 AI_OpponentCanFaintAiWithMod(enum BattlerId battler, u32 healAmount);
void SetBattlerFieldStatusForSwitchin(enum BattlerId battler);
bool32 ShouldInstructPartner(enum BattlerId partner, enum Move move);
bool32 CanMoveBeBouncedBack(enum BattlerId battler, enum Move move);

View File

@ -423,5 +423,6 @@ void TryResetConsecutiveUseCounter(enum BattlerId battler);
void SetOrClearRageVolatile(void);
enum BattlerId GetTargetBySlot(enum BattlerId battlerAtk, enum BattlerId battlerDef);
bool32 IsNaturalEnemy(enum Species speciesAttacker, enum Species speciesTarget);
enum Stat GetDownloadStat(enum BattlerId battler);
#endif // GUARD_BATTLE_UTIL_H

View File

@ -35,11 +35,17 @@ struct IncomingHealInfo
static bool32 CanUseSuperEffectiveMoveAgainstOpponents(enum BattlerId battler);
static bool32 FindMonWithFlagsAndSuperEffective(enum BattlerId battler, u16 flags, u32 moduloPercent);
static u32 GetSwitchinHazardsDamage(enum BattlerId battler);
static u32 GetSwitchinSingleUseItemHealing(enum BattlerId battler, enum BattlerId opposingBattler, s32 currentHP);
static bool32 AI_CanSwitchinAbilityTrapOpponent(enum Ability ability, enum BattlerId opposingBattler);
static u32 GetBattlerTypeMatchup(enum BattlerId opposingBattler, enum BattlerId battler);
static u32 GetSwitchinHitsToKO(s32 damageTaken, enum BattlerId battler, const struct IncomingHealInfo *healInfo, u32 originalHp);
static void GetIncomingHealInfo(enum BattlerId battler, struct IncomingHealInfo *healInfo);
static u32 GetWishHealAmountForBattler(enum BattlerId battler);
static void SetBattlerStatusForSwitchin(enum BattlerId battler);
static void SetBattlerStatStagesForSwitchin(enum BattlerId battler, enum BattlerId opposingBattler, u32 fieldStatus);
static void SetBattlerHPChangeForSwitch(enum BattlerId battler, enum BattlerId opposingBattler);
static void SetBattlerVolatilesForSwitchin(enum BattlerId battler, u32 weather, u32 fieldStatus);
bool32 IsSwitchinTSpikesAffected(enum BattlerId battler);
static void InitializeSwitchinCandidate(enum BattlerId switchinBattler, u32 monIndex, struct Pokemon *mon)
{
@ -48,15 +54,19 @@ static void InitializeSwitchinCandidate(enum BattlerId switchinBattler, u32 monI
// Setup switchin battler data
gAiThinkingStruct->saved[switchinBattler].saved = TRUE;
SetBattlerAiData(switchinBattler, gAiLogicData);
SetBattlerFieldStatusForSwitchin(switchinBattler);
u32 switchinWeather = AI_GetSwitchinWeather(switchinBattler);
u32 switchinFieldStatus = AI_GetSwitchinFieldStatus(switchinBattler);
SetBattlerVolatilesForSwitchin(switchinBattler, switchinWeather, switchinFieldStatus);
SetBattlerStatusForSwitchin(switchinBattler);
gBattlerPartyIndexes[switchinBattler] = monIndex;
for (enum BattlerId battlerIndex = 0; battlerIndex < gBattlersCount; battlerIndex++)
{
if (switchinBattler == battlerIndex || !IsBattlerAlive(battlerIndex))
continue;
CalcBattlerAiMovesData(gAiLogicData, switchinBattler, battlerIndex, AI_GetSwitchinWeather(switchinBattler), AI_GetSwitchinFieldStatus(switchinBattler));
CalcBattlerAiMovesData(gAiLogicData, battlerIndex, switchinBattler, AI_GetSwitchinWeather(switchinBattler), AI_GetSwitchinFieldStatus(switchinBattler));
SetBattlerStatStagesForSwitchin(switchinBattler, battlerIndex, switchinFieldStatus);
SetBattlerHPChangeForSwitch(switchinBattler, battlerIndex);
CalcBattlerAiMovesData(gAiLogicData, switchinBattler, battlerIndex, switchinWeather, switchinFieldStatus);
CalcBattlerAiMovesData(gAiLogicData, battlerIndex, switchinBattler, switchinWeather, switchinFieldStatus);
}
gBattlerPartyIndexes[switchinBattler] = storeCurrBattlerPartyIndex;
@ -218,6 +228,35 @@ static bool32 AreStatsRaised(enum BattlerId battler)
return (buffedStatsValue > STAY_IN_STATS_RAISED);
}
bool32 IsSwitchinTSpikesAffected(enum BattlerId battler)
{
enum Ability ability = gAiLogicData->abilities[battler];
enum HoldEffect heldItemEffect = gAiLogicData->holdEffects[battler];
enum BattlerId opposingBattler = GetOppositeBattler(battler);
bool32 ignoreItem = ((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || ability == ABILITY_KLUTZ);
if (gBattleMons[battler].status1 & STATUS1_ANY)
return FALSE;
if (IS_BATTLER_ANY_TYPE(battler, TYPE_POISON, TYPE_STEEL))
return FALSE;
if (ability == ABILITY_IMMUNITY || IsAbilityOnSide(battler, ABILITY_PASTEL_VEIL))
return FALSE;
if ((heldItemEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS || heldItemEffect == HOLD_EFFECT_CURE_PSN || heldItemEffect == HOLD_EFFECT_CURE_STATUS) && !ignoreItem)
return FALSE;
if (!AI_IsBattlerGrounded(battler))
return FALSE;
if (IsMistyTerrainAffected(battler, ability, heldItemEffect, gFieldStatuses))
return FALSE;
if (IsLeafGuardProtected(battler, ability))
return FALSE;
if (IsShieldsDownProtected(battler, ability))
return FALSE;
if (IsFlowerVeilProtected(battler))
return FALSE;
if (IsSafeguardProtected(opposingBattler, battler, gAiLogicData->abilities[opposingBattler]))
return FALSE;
return TRUE;
}
static inline bool32 SetSwitchinAndSwitch(enum BattlerId battler, u32 switchinId)
{
gBattleStruct->AI_monToSwitchIntoId[battler] = switchinId;
@ -1488,6 +1527,51 @@ bool32 IsSwitchinValid(enum BattlerId battler)
return TRUE;
}
static u32 GetSwitchinSingleUseItemHealing(enum BattlerId battler, enum BattlerId opposingBattler, s32 currentHP)
{
enum Item aiItem = gAiLogicData->items[battler];
u32 maxHP = gBattleMons[battler].maxHP;
s32 itemHeal = 0;
// Check if we're at a single use healing item threshold
if (currentHP <= 0
|| gAiLogicData->abilities[battler] == ABILITY_KLUTZ
|| (gAiLogicData->abilities[opposingBattler] == ABILITY_UNNERVE && GetItemPocket(aiItem) == POCKET_BERRIES))
return itemHeal;
switch (GetItemHoldEffect(aiItem))
{
case HOLD_EFFECT_RESTORE_HP:
if (currentHP < maxHP / 2)
itemHeal = GetItemHoldEffectParam(aiItem);
break;
case HOLD_EFFECT_RESTORE_PCT_HP:
if (currentHP < maxHP / 2)
{
itemHeal = maxHP / GetItemHoldEffectParam(aiItem);
if (itemHeal == 0)
itemHeal = 1;
}
break;
case HOLD_EFFECT_CONFUSE_SPICY:
case HOLD_EFFECT_CONFUSE_DRY:
case HOLD_EFFECT_CONFUSE_SWEET:
case HOLD_EFFECT_CONFUSE_BITTER:
case HOLD_EFFECT_CONFUSE_SOUR:
if (currentHP < maxHP / CONFUSE_BERRY_HP_FRACTION)
{
itemHeal = maxHP / GetItemHoldEffectParam(aiItem);
if (itemHeal == 0)
itemHeal = 1;
}
break;
default:
break;
}
return itemHeal;
}
// Gets hazard damage
static u32 GetSwitchinHazardsDamage(enum BattlerId battler)
{
@ -1681,7 +1765,6 @@ static u32 GetSwitchinRecurringDamage(enum BattlerId battler)
static u32 GetSwitchinStatusDamage(enum BattlerId battler)
{
u8 tSpikesLayers = gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount;
enum HoldEffect heldItemEffect = gAiLogicData->holdEffects[battler];
u32 status = gBattleMons[battler].status1;
enum Ability ability = gAiLogicData->abilities[battler];
u32 maxHP = gBattleMons[battler].maxHP;
@ -1729,13 +1812,7 @@ static u32 GetSwitchinStatusDamage(enum BattlerId battler)
// Apply hypothetical poisoning from Toxic Spikes, which means the first turn of damage already added in GetSwitchinHazardsDamage
// Do this last to skip one iteration of Poison / Toxic damage, and start counting Toxic damage one turn later.
if (tSpikesLayers != 0 && (!IS_BATTLER_ANY_TYPE(battler, TYPE_POISON, TYPE_STEEL)
&& ability != ABILITY_IMMUNITY && ability != ABILITY_POISON_HEAL && ability != ABILITY_PASTEL_VEIL
&& status == 0
&& !(heldItemEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS
&& (((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || ability == ABILITY_KLUTZ)))
&& heldItemEffect != HOLD_EFFECT_CURE_PSN && heldItemEffect != HOLD_EFFECT_CURE_STATUS
&& AI_IsBattlerGrounded(battler)))
if (tSpikesLayers != 0 && IsSwitchinTSpikesAffected(battler))
{
if (tSpikesLayers == 1)
{
@ -1777,7 +1854,7 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, enum BattlerId battler, const st
u32 statusDamage = GetSwitchinStatusDamage(battler);
u32 hitsToKO = 0;
u16 maxHP = gBattleMons[battler].maxHP, item = gAiLogicData->items[battler], heldItemEffect = GetItemHoldEffect(item);
u8 weatherDuration = gBattleStruct->weatherDuration, holdEffectParam = GetItemHoldEffectParam(item);
u8 weatherDuration = gBattleStruct->weatherDuration;
enum BattlerId opposingBattler = GetOppositeBattler(battler);
enum Ability opposingAbility = gAiLogicData->abilities[opposingBattler], ability = gAiLogicData->abilities[battler];
bool32 usedSingleUseHealingItem = FALSE, opponentCanBreakMold = IsMoldBreakerTypeAbility(opposingBattler, opposingAbility);
@ -1811,37 +1888,9 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, enum BattlerId battler, const st
currentHP = currentHP - weatherImpact;
// Check if we're at a single use healing item threshold
if (currentHP > 0 && gAiLogicData->abilities[battler] != ABILITY_KLUTZ && usedSingleUseHealingItem == FALSE
&& !(opposingAbility == ABILITY_UNNERVE && GetItemPocket(item) == POCKET_BERRIES))
if (usedSingleUseHealingItem == FALSE)
{
switch (heldItemEffect)
{
case HOLD_EFFECT_RESTORE_HP:
if (currentHP < maxHP / 2)
singleUseItemHeal = holdEffectParam;
break;
case HOLD_EFFECT_RESTORE_PCT_HP:
if (currentHP < maxHP / 2)
{
singleUseItemHeal = maxHP / holdEffectParam;
if (singleUseItemHeal == 0)
singleUseItemHeal = 1;
}
break;
case HOLD_EFFECT_CONFUSE_SPICY:
case HOLD_EFFECT_CONFUSE_DRY:
case HOLD_EFFECT_CONFUSE_SWEET:
case HOLD_EFFECT_CONFUSE_BITTER:
case HOLD_EFFECT_CONFUSE_SOUR:
if (currentHP < maxHP / CONFUSE_BERRY_HP_FRACTION)
{
singleUseItemHeal = maxHP / holdEffectParam;
if (singleUseItemHeal == 0)
singleUseItemHeal = 1;
}
break;
}
singleUseItemHeal = GetSwitchinSingleUseItemHealing(battler, opposingBattler, currentHP);
// If we used one, apply it without overcapping our maxHP
if (singleUseItemHeal > 0)
{
@ -2611,3 +2660,194 @@ u32 AI_SelectRevivalBlessingMon(enum BattlerId battler)
return bestMonId;
}
static void SetBattlerStatusForSwitchin(enum BattlerId battler)
{
u32 tSpikesLayers = gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount;
if (tSpikesLayers != 0 && IsSwitchinTSpikesAffected(battler))
{
if (tSpikesLayers == 1)
gBattleMons[battler].status1 = STATUS1_POISON;
if (tSpikesLayers == 2)
{
gBattleMons[battler].status1 = STATUS1_TOXIC_POISON;
gBattleMons[battler].status1 += STATUS1_TOXIC_TURN(1);
}
}
}
static void SetBattlerStatStagesForSwitchin(enum BattlerId battler, enum BattlerId opposingBattler, u32 fieldStatus)
{
u32 aiAbility = gAiLogicData->abilities[battler];
enum Item aiItem = gAiLogicData->items[battler];
bool32 isStickyWebsAffected = (IsHazardOnSide(GetBattlerSide(battler), HAZARDS_STICKY_WEB) && IsBattlerAffectedByHazards(battler, GetItemHoldEffect(aiItem), FALSE) && IsBattlerGrounded(battler, gAiLogicData->abilities[battler], GetItemHoldEffect(aiItem)));
bool32 opponentStatDrop = FALSE;
// Ability stat changes
switch(aiAbility)
{
case ABILITY_INTREPID_SWORD:
gBattleMons[battler].statStages[STAT_ATK] += 1;
break;
case ABILITY_DAUNTLESS_SHIELD:
gBattleMons[battler].statStages[STAT_DEF] += 1;
break;
case ABILITY_SUPREME_OVERLORD:
break;
case ABILITY_DOWNLOAD:
gBattleMons[battler].statStages[GetDownloadStat(battler)] += 1;
break;
case ABILITY_INTIMIDATE:
if (CanLowerStat(battler, opposingBattler, gAiLogicData, STAT_ATK))
{
if (gAiLogicData->abilities[opposingBattler] == ABILITY_CONTRARY)
{
gBattleMons[opposingBattler].statStages[STAT_ATK] += 1;
}
else
{
opponentStatDrop = TRUE;
gBattleMons[opposingBattler].statStages[STAT_ATK] -= 1;
if (gAiLogicData->abilities[opposingBattler] == ABILITY_DEFIANT)
gBattleMons[opposingBattler].statStages[STAT_ATK] += 2;
if (gAiLogicData->abilities[opposingBattler] == ABILITY_COMPETITIVE)
gBattleMons[opposingBattler].statStages[STAT_SPATK] += 2;
}
}
break;
case ABILITY_SUPERSWEET_SYRUP:
if (CanLowerStat(battler, opposingBattler, gAiLogicData, STAT_EVASION))
{
if (gAiLogicData->abilities[opposingBattler] == ABILITY_CONTRARY)
{
gBattleMons[opposingBattler].statStages[STAT_EVASION] += 1;
}
else
{
opponentStatDrop = TRUE;
gBattleMons[opposingBattler].statStages[STAT_EVASION] -= 1;
if (gAiLogicData->abilities[opposingBattler] == ABILITY_DEFIANT)
gBattleMons[opposingBattler].statStages[STAT_ATK] += 2;
if (gAiLogicData->abilities[opposingBattler] == ABILITY_COMPETITIVE)
gBattleMons[opposingBattler].statStages[STAT_SPATK] += 2;
}
}
break;
case ABILITY_WIND_RIDER:
if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_TAILWIND)
gBattleMons[battler].statStages[STAT_ATK] += 1;
break;
case ABILITY_DEFIANT:
if (isStickyWebsAffected)
gBattleMons[battler].statStages[STAT_ATK] += 2;
break;
case ABILITY_COMPETITIVE:
if (isStickyWebsAffected)
gBattleMons[battler].statStages[STAT_SPATK] += 2;
break;
case ABILITY_CONTRARY:
if (isStickyWebsAffected)
gBattleMons[battler].statStages[STAT_SPEED] += 1;
default:
break;
}
// Item stat changes
switch(GetItemHoldEffect(aiItem))
{
case HOLD_EFFECT_TERRAIN_SEED:
u32 seedParam = GetItemHoldEffectParam(aiItem);
if ((seedParam == HOLD_EFFECT_PARAM_ELECTRIC_TERRAIN && (fieldStatus & STATUS_FIELD_ELECTRIC_TERRAIN))
|| (seedParam == HOLD_EFFECT_PARAM_GRASSY_TERRAIN && (fieldStatus & STATUS_FIELD_GRASSY_TERRAIN))
|| (seedParam == HOLD_EFFECT_PARAM_MISTY_TERRAIN && (fieldStatus & STATUS_FIELD_MISTY_TERRAIN))
|| (seedParam == HOLD_EFFECT_PARAM_PSYCHIC_TERRAIN && (fieldStatus & STATUS_FIELD_PSYCHIC_TERRAIN)))
gBattleMons[battler].statStages[STAT_DEF] += 1;
break;
case HOLD_EFFECT_ATTACK_UP:
if (HasEnoughHpToEatBerry(battler, aiAbility, GetItemHoldEffectParam(aiItem), aiItem))
gBattleMons[battler].statStages[STAT_ATK] += 1;
break;
case HOLD_EFFECT_DEFENSE_UP:
if (HasEnoughHpToEatBerry(battler, aiAbility, GetItemHoldEffectParam(aiItem), aiItem))
gBattleMons[battler].statStages[STAT_DEF] += 1;
break;
case HOLD_EFFECT_SPEED_UP:
if (HasEnoughHpToEatBerry(battler, aiAbility, GetItemHoldEffectParam(aiItem), aiItem))
gBattleMons[battler].statStages[STAT_SPEED] += 1;
break;
case HOLD_EFFECT_SP_ATTACK_UP:
if (HasEnoughHpToEatBerry(battler, aiAbility, GetItemHoldEffectParam(aiItem), aiItem))
gBattleMons[battler].statStages[STAT_SPATK] += 1;
break;
case HOLD_EFFECT_SP_DEFENSE_UP:
if (HasEnoughHpToEatBerry(battler, aiAbility, GetItemHoldEffectParam(aiItem), aiItem))
gBattleMons[battler].statStages[STAT_SPDEF] += 1;
break;
case HOLD_EFFECT_ROOM_SERVICE:
if (gFieldStatuses & STATUS_FIELD_TRICK_ROOM)
gBattleMons[battler].statStages[STAT_SPEED] -= 1;
case HOLD_EFFECT_MIRROR_HERB:
if (opponentStatDrop && gAiLogicData->abilities[opposingBattler] == ABILITY_DEFIANT)
gBattleMons[battler].statStages[STAT_ATK] += 2;
if (opponentStatDrop && gAiLogicData->abilities[opposingBattler] == ABILITY_COMPETITIVE)
gBattleMons[battler].statStages[STAT_SPATK] += 2;
break;
default:
break;
}
// Hazard stat changes
if (isStickyWebsAffected && GetItemHoldEffect(aiItem) != HOLD_EFFECT_WHITE_HERB)
gBattleMons[battler].statStages[STAT_SPEED] -= 1;
}
static void SetBattlerHPChangeForSwitch(enum BattlerId battler, enum BattlerId opposingBattler)
{
s32 maxHP = gBattleMons[battler].maxHP;
s32 currentHP = gBattleMons[battler].hp - GetSwitchinHazardsDamage(battler);
s32 itemHeal = GetSwitchinSingleUseItemHealing(battler, opposingBattler, currentHP);
if (itemHeal > 0)
{
if ((currentHP + itemHeal) > maxHP)
currentHP = maxHP;
else
currentHP = currentHP + itemHeal;
}
gBattleMons[battler].hp = currentHP;
}
// Set potential field effect from ability for switch in
static void SetBattlerVolatilesForSwitchin(enum BattlerId battler, u32 weather, u32 fieldStatus)
{
enum Item aiItem = gAiLogicData->items[battler];
switch (gAiLogicData->abilities[battler])
{
case ABILITY_VESSEL_OF_RUIN:
gBattleMons[battler].volatiles.vesselOfRuin = TRUE;
break;
case ABILITY_SWORD_OF_RUIN:
gBattleMons[battler].volatiles.swordOfRuin = TRUE;
break;
case ABILITY_TABLETS_OF_RUIN:
gBattleMons[battler].volatiles.tabletsOfRuin = TRUE;
break;
case ABILITY_BEADS_OF_RUIN:
gBattleMons[battler].volatiles.beadsOfRuin = TRUE;
break;
case ABILITY_QUARK_DRIVE:
if ((fieldStatus & STATUS_FIELD_ELECTRIC_TERRAIN) || GetItemHoldEffect(aiItem) == HOLD_EFFECT_BOOSTER_ENERGY)
gBattleMons[battler].volatiles.boosterEnergyActivated = TRUE;
break;
case ABILITY_PROTOSYNTHESIS:
if (((weather & B_WEATHER_SUN) && HasWeatherEffect()) || GetItemHoldEffect(aiItem) == HOLD_EFFECT_BOOSTER_ENERGY)
gBattleMons[battler].volatiles.boosterEnergyActivated = TRUE;
break;
case ABILITY_WIND_POWER:
if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_TAILWIND)
gBattleMons[battler].volatiles.chargeTimer = 2;
break;
default:
break;
}
}

View File

@ -1873,6 +1873,7 @@ u32 AI_GetSwitchinWeather(enum BattlerId battler)
case ABILITY_DRIZZLE:
return B_WEATHER_RAIN_NORMAL;
case ABILITY_DROUGHT:
case ABILITY_ORICHALCUM_PULSE:
return B_WEATHER_SUN_NORMAL;
case ABILITY_SAND_STREAM:
return B_WEATHER_SANDSTORM;
@ -4531,28 +4532,6 @@ void FreeRestoreAiLogicData(struct AiLogicData *savedAiLogicData)
Free(savedAiLogicData);
}
// Set potential field effect from ability for switch in
void SetBattlerFieldStatusForSwitchin(enum BattlerId battler)
{
switch (gAiLogicData->abilities[battler])
{
case ABILITY_VESSEL_OF_RUIN:
gBattleMons[battler].volatiles.vesselOfRuin = TRUE;
break;
case ABILITY_SWORD_OF_RUIN:
gBattleMons[battler].volatiles.swordOfRuin = TRUE;
break;
case ABILITY_TABLETS_OF_RUIN:
gBattleMons[battler].volatiles.tabletsOfRuin = TRUE;
break;
case ABILITY_BEADS_OF_RUIN:
gBattleMons[battler].volatiles.beadsOfRuin = TRUE;
break;
default:
break;
}
}
// party logic
s32 CountUsablePartyMons(enum BattlerId battlerId)
{

View File

@ -3239,28 +3239,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum
case ABILITY_DOWNLOAD:
if (shouldAbilityTrigger)
{
enum Stat statId;
enum BattlerId opposingBattler;
u32 opposingDef = 0, opposingSpDef = 0;
opposingBattler = BATTLE_OPPOSITE(battler);
for (i = 0; i < 2; opposingBattler ^= BIT_FLANK, i++)
{
if (IsBattlerAlive(opposingBattler))
{
opposingDef += gBattleMons[opposingBattler].defense
* gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_DEF]][0]
/ gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_DEF]][1];
opposingSpDef += gBattleMons[opposingBattler].spDefense
* gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_SPDEF]][0]
/ gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_SPDEF]][1];
}
}
if (opposingDef < opposingSpDef)
statId = STAT_ATK;
else
statId = STAT_SPATK;
enum Stat statId = GetDownloadStat(battler);
if (CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility))
{
@ -10939,3 +10918,28 @@ bool32 IsNaturalEnemy(enum Species speciesAttacker, enum Species speciesTarget)
}
return FALSE;
}
enum Stat GetDownloadStat(enum BattlerId battler)
{
enum BattlerId opposingBattler;
u32 opposingDef = 0, opposingSpDef = 0;
opposingBattler = BATTLE_OPPOSITE(battler);
for (u32 i = 0; i < 2; opposingBattler ^= BIT_FLANK, i++)
{
if (IsBattlerAlive(opposingBattler))
{
opposingDef += gBattleMons[opposingBattler].defense
* gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_DEF]][0]
/ gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_DEF]][1];
opposingSpDef += gBattleMons[opposingBattler].spDefense
* gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_SPDEF]][0]
/ gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_SPDEF]][1];
}
}
if (opposingDef < opposingSpDef)
return STAT_ATK;
else
return STAT_SPATK;
}

View File

@ -2244,3 +2244,60 @@ AI_DOUBLE_BATTLE_TEST("AI can switch out both mons on the same turn in double ba
TURN { EXPECT_SWITCH(opponentLeft, 2); EXPECT_SWITCH(opponentRight, 3); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI sees stat stage changes on switchin when doing switching calcs")
{
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);
PLAYER(SPECIES_LUXIO) { Speed(2); Ability(ABILITY_GUTS); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); HP(1); MaxHP(100); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_LUXIO) { Speed(1); Ability(ABILITY_GUTS); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_LUXIO) { Speed(1); Ability(ABILITY_INTIMIDATE); Moves(MOVE_SCRATCH); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 2); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI sees status changes on switchin when doing switching calcs")
{
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);
PLAYER(SPECIES_ZIGZAGOON) { Speed(2); HP(1); MaxHP(100); Moves(MOVE_TOXIC_SPIKES); }
PLAYER(SPECIES_LUXIO) { Speed(2); Ability(ABILITY_GUTS); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); HP(1); MaxHP(100); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_LUXIO) { Speed(1); Ability(ABILITY_INTIMIDATE); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_LUXIO) { Speed(1); Ability(ABILITY_GUTS); Moves(MOVE_SCRATCH); }
} WHEN {
TURN { MOVE(player, MOVE_TOXIC_SPIKES); EXPECT_MOVE(opponent, MOVE_SCRATCH); SEND_OUT(player, 1); }
TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 2); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI sees volate changes on switchin when doing switching calcs")
{
GIVEN {
ASSUME(GetItemHoldEffect(ITEM_BOOSTER_ENERGY) == HOLD_EFFECT_BOOSTER_ENERGY);
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);
PLAYER(SPECIES_ROARING_MOON) { Speed(2); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); HP(1); MaxHP(100); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_ROARING_MOON) { Speed(1); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_ROARING_MOON) { Speed(1); Moves(MOVE_SCRATCH); Item(ITEM_BOOSTER_ENERGY); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 2); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI sees HP changes on switchin when doing switchin calcs")
{
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);
PLAYER(SPECIES_ZIGZAGOON) { Speed(2); HP(1); MaxHP(100); Moves(MOVE_SPIKES); }
PLAYER(SPECIES_BLAZIKEN) { Speed(2); HP(130); MaxHP(400); Ability(ABILITY_SPEED_BOOST); Moves(MOVE_EMBER); }
OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); HP(1); MaxHP(100); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_BLAZIKEN) { Speed(1); HP(180); MaxHP(400); Ability(ABILITY_SPEED_BOOST); Moves(MOVE_EMBER); }
OPPONENT(SPECIES_BLAZIKEN) { Speed(1); HP(180); MaxHP(400); Ability(ABILITY_BLAZE); Moves(MOVE_EMBER); }
} WHEN {
TURN { MOVE(player, MOVE_SPIKES); EXPECT_MOVE(opponent, MOVE_SCRATCH); SEND_OUT(player, 1); }
TURN { MOVE(player, MOVE_EMBER); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 2); }
}
}