Switch AI refactor + considers free switches (#5379)
* Switch AI considers free switches from pivot move
* Fix Baton Pass refactor
* Some cleanup and comments
* Mon fainting to hazards is a 0HKO
* Revert "Mon fainting to hazards is a 0HKO"
This reverts commit 446f738226
.
* Cleanup speed check / Eject Pack
* Move eject trackers to AiLogicData
* Review feedback and WhoStrikesFirst changes
* This linebreak will bug me lol
* Also this comment, heck
* Last bit of comment cleanup
This commit is contained in:
parent
5a8a393dcd
commit
ded97e5296
8 changed files with 211 additions and 103 deletions
|
@ -374,6 +374,8 @@ struct AiLogicData
|
|||
bool8 weatherHasEffect; // The same as WEATHER_HAS_EFFECT. Stored here, so it's called only once.
|
||||
u8 mostSuitableMonId[MAX_BATTLERS_COUNT]; // Stores result of GetMostSuitableMonToSwitchInto, which decides which generic mon the AI would switch into if they decide to switch. This can be overruled by specific mons found in ShouldSwitch; the final resulting mon is stored in AI_monToSwitchIntoId.
|
||||
struct SwitchinCandidate switchinCandidate; // Struct used for deciding which mon to switch to in battle_ai_switch_items.c
|
||||
bool8 ejectButtonSwitch; // Tracks whether current switch out was from Eject Button
|
||||
bool8 ejectPackSwitch; // Tracks whether current switch out was from Eject Pack
|
||||
u8 shouldSwitch; // Stores result of ShouldSwitch, which decides whether a mon should be switched out
|
||||
};
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ void RecordItemEffectBattle(u32 battlerId, u32 itemEffect);
|
|||
void ClearBattlerItemEffectHistory(u32 battlerId);
|
||||
void SaveBattlerData(u32 battlerId);
|
||||
void SetBattlerData(u32 battlerId);
|
||||
void SetBattlerAiData(u32 battlerId, struct AiLogicData *aiData);
|
||||
void RestoreBattlerData(u32 battlerId);
|
||||
u32 GetAIChosenMove(u32 battlerId);
|
||||
u32 GetTotalBaseStat(u32 species);
|
||||
|
@ -140,6 +141,7 @@ bool32 HasThawingMove(u32 battler);
|
|||
bool32 IsStatRaisingEffect(u32 effect);
|
||||
bool32 IsStatLoweringEffect(u32 effect);
|
||||
bool32 IsSelfStatLoweringEffect(u32 effect);
|
||||
bool32 IsSwitchOutEffect(u32 effect);
|
||||
bool32 IsAttackBoostMoveEffect(u32 effect);
|
||||
bool32 IsUngroundingEffect(u32 effect);
|
||||
bool32 IsSemiInvulnerable(u32 battlerDef, u32 move);
|
||||
|
@ -201,6 +203,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, bool32 isPartyMonAttacker, enum DamageRollType rollType);
|
||||
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);
|
||||
bool32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData);
|
||||
|
|
|
@ -389,7 +389,7 @@ void Ai_UpdateFaintData(u32 battler)
|
|||
aiMon->isFainted = TRUE;
|
||||
}
|
||||
|
||||
static void SetBattlerAiData(u32 battler, struct AiLogicData *aiData)
|
||||
void SetBattlerAiData(u32 battler, struct AiLogicData *aiData)
|
||||
{
|
||||
u32 ability, holdEffect;
|
||||
|
||||
|
|
|
@ -1789,30 +1789,60 @@ static bool32 CanAbilityTrapOpponent(u16 ability, u32 opponent)
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
// This function splits switching behaviour mid-battle from after a KO.
|
||||
// Mid battle, it integrates GetBestMonTypeMatchup (vanilla with modifications), GetBestMonDefensive (custom), and GetBestMonBatonPass (vanilla with modifications)
|
||||
// After a KO, integrates GetBestMonRevengeKiller (custom), GetBestMonTypeMatchup (vanilla with modifications), GetBestMonBatonPass (vanilla with modifications), and GetBestMonDmg (vanilla)
|
||||
// the Type Matchup code will prioritize switching into a mon with the best type matchup and also a super effective move, or just best type matchup if no super effective move is found
|
||||
// the Most Defensive code will prioritize switching into the mon that takes the most hits to KO, with a minimum of 4 hits required to be considered a valid option
|
||||
// the Baton Pass code will prioritize switching into a mon with Baton Pass if it can get in, boost, and BP out without being KO'd, and randomizes between multiple valid options
|
||||
// the Revenge Killer code will prioritize, in order, OHKO and outspeeds / OHKO, slower but not 2HKO'd / 2HKO, outspeeds and not OHKO'd / 2HKO, slower but not 3HKO'd
|
||||
// the Most Damage code will prioritize switching into whatever mon deals the most damage, which is generally not as good as having a good Type Matchup
|
||||
// Everything runs in the same loop to minimize computation time. This makes it harder to read, but hopefully the comments can guide you!
|
||||
static inline bool32 IsFreeSwitch(bool32 isSwitchAfterKO, u32 battlerSwitchingOut, u32 opposingBattler)
|
||||
{
|
||||
bool32 movedSecond = GetBattlerTurnOrderNum(battlerSwitchingOut) > GetBattlerTurnOrderNum(opposingBattler) ? TRUE : FALSE;
|
||||
|
||||
// Switch out effects
|
||||
if (!IsDoubleBattle()) // Not handling doubles' additional complexity
|
||||
{
|
||||
if (IsSwitchOutEffect(gMovesInfo[gLastUsedMove].effect) && movedSecond)
|
||||
return TRUE;
|
||||
if (AI_DATA->ejectButtonSwitch)
|
||||
return TRUE;
|
||||
if (AI_DATA->ejectPackSwitch)
|
||||
{
|
||||
// If faster, not a free switch; likely lowered own stats
|
||||
if (!movedSecond)
|
||||
return FALSE;
|
||||
// Otherwise, free switch
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Post KO check has to be last because the GetMostSuitableMonToSwitchInto call in OpponentHandleChoosePokemon assumes a KO rather than a forced switch choice
|
||||
if (isSwitchAfterKO)
|
||||
return TRUE;
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static inline bool32 CanSwitchinWin1v1(u32 hitsToKOAI, u32 hitsToKOPlayer, bool32 isSwitchinFirst, bool32 isFreeSwitch)
|
||||
{
|
||||
// Free switch, need to outspeed or take 1 extra hit
|
||||
if (isFreeSwitch)
|
||||
{
|
||||
if (hitsToKOAI > hitsToKOPlayer || (hitsToKOAI == hitsToKOPlayer && isSwitchinFirst))
|
||||
return TRUE;
|
||||
}
|
||||
// Mid battle switch, need to take 1 or 2 extra hits depending on speed
|
||||
if (hitsToKOAI > hitsToKOPlayer + 1 || (hitsToKOAI == hitsToKOPlayer + 1 && isSwitchinFirst))
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// This function splits switching behaviour depending on whether the switch is free.
|
||||
// Everything runs in the same loop to minimize computation time. This makes it harder to read, but hopefully the comments can guide you!
|
||||
static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, u32 battler, u32 opposingBattler, u32 battlerIn1, u32 battlerIn2, bool32 isSwitchAfterKO)
|
||||
{
|
||||
int revengeKillerId = PARTY_SIZE, slowRevengeKillerId = PARTY_SIZE, fastThreatenId = PARTY_SIZE, slowThreatenId = PARTY_SIZE, damageMonId = PARTY_SIZE;
|
||||
int batonPassId = PARTY_SIZE, typeMatchupId = PARTY_SIZE, typeMatchupEffectiveId = PARTY_SIZE, defensiveMonId = PARTY_SIZE, aceMonId = PARTY_SIZE, trapperId = PARTY_SIZE;
|
||||
int i, j, aliveCount = 0, bits = 0;
|
||||
s32 defensiveMonHitKOThreshold = 3; // 3HKO threshold that candidate defensive mons must exceed
|
||||
u32 aiMove, hitsToKOAI, hitsToKOPlayer, hitsToKOAIThreshold, maxHitsToKO = 0;
|
||||
s32 playerMonSpeed = gBattleMons[opposingBattler].speed, playerMonHP = gBattleMons[opposingBattler].hp, aiMonSpeed, aiMovePriority = 0, maxDamageDealt = 0, damageDealt = 0;
|
||||
s32 playerMonHP = gBattleMons[opposingBattler].hp, maxDamageDealt = 0, damageDealt = 0;
|
||||
u32 aiMove, hitsToKOAI, maxHitsToKO = 0;
|
||||
u16 bestResist = UQ_4_12(1.0), bestResistEffective = UQ_4_12(1.0), typeMatchup;
|
||||
|
||||
if (isSwitchAfterKO)
|
||||
hitsToKOAIThreshold = 1; // After a KO, mons at minimum need to not be 1-shot, as they switch in for free
|
||||
else
|
||||
hitsToKOAIThreshold = 2; // When switching in otherwise need to not be 2-shot, as they do not switch in for free
|
||||
bool32 isFreeSwitch = IsFreeSwitch(isSwitchAfterKO, battlerIn1, opposingBattler), isSwitchinFirst, canSwitchinWin1v1;
|
||||
|
||||
// Iterate through mons
|
||||
for (i = firstId; i < lastId; i++)
|
||||
|
@ -1841,10 +1871,11 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
if (AI_DATA->switchinCandidate.battleMon.ability == ABILITY_TRUANT && IsTruantMonVulnerable(battler, opposingBattler))
|
||||
continue;
|
||||
|
||||
// Get max number of hits for player to KO AI mon
|
||||
// Get max number of hits for player to KO AI mon and type matchup for defensive switching
|
||||
hitsToKOAI = GetSwitchinHitsToKO(GetMaxDamagePlayerCouldDealToSwitchin(battler, opposingBattler, AI_DATA->switchinCandidate.battleMon), battler);
|
||||
typeMatchup = GetSwitchinTypeMatchup(opposingBattler, AI_DATA->switchinCandidate.battleMon);
|
||||
|
||||
// Track max hits to KO and set GetBestMonDefensive if applicable
|
||||
// Track max hits to KO and set defensive mon
|
||||
if(hitsToKOAI > maxHitsToKO)
|
||||
{
|
||||
maxHitsToKO = hitsToKOAI;
|
||||
|
@ -1852,28 +1883,12 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
defensiveMonId = i;
|
||||
}
|
||||
|
||||
typeMatchup = GetSwitchinTypeMatchup(opposingBattler, AI_DATA->switchinCandidate.battleMon);
|
||||
|
||||
// Check that good type matchups gets at least two turns and set GetBestMonTypeMatchup if applicable
|
||||
if (typeMatchup < bestResist)
|
||||
{
|
||||
if ((hitsToKOAI > hitsToKOAIThreshold && AI_DATA->switchinCandidate.battleMon.speed > playerMonSpeed) || hitsToKOAI > hitsToKOAIThreshold + 1) // Need to take an extra hit if slower
|
||||
{
|
||||
bestResist = typeMatchup;
|
||||
typeMatchupId = i;
|
||||
}
|
||||
}
|
||||
|
||||
aiMonSpeed = AI_DATA->switchinCandidate.battleMon.speed;
|
||||
|
||||
// Check through current mon's moves
|
||||
for (j = 0; j < MAX_MON_MOVES; j++)
|
||||
{
|
||||
aiMove = AI_DATA->switchinCandidate.battleMon.moves[j];
|
||||
aiMovePriority = gMovesInfo[aiMove].priority;
|
||||
|
||||
// Only do damage calc if switching after KO, don't need it otherwise and saves ~0.02s per turn
|
||||
if (isSwitchAfterKO && aiMove != MOVE_NONE && gMovesInfo[aiMove].power != 0)
|
||||
if (aiMove != MOVE_NONE && gMovesInfo[aiMove].power != 0)
|
||||
{
|
||||
if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_CONSERVATIVE)
|
||||
damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE, DMG_ROLL_LOWEST);
|
||||
|
@ -1881,19 +1896,35 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE, DMG_ROLL_DEFAULT);
|
||||
}
|
||||
|
||||
// Check for Baton Pass; hitsToKO requirements mean mon can boost and BP without dying whether it's slower or not
|
||||
if (aiMove == MOVE_BATON_PASS && ((hitsToKOAI > hitsToKOAIThreshold + 1 && AI_DATA->switchinCandidate.battleMon.speed < playerMonSpeed) || (hitsToKOAI > hitsToKOAIThreshold && AI_DATA->switchinCandidate.battleMon.speed > playerMonSpeed)))
|
||||
bits |= 1u << i;
|
||||
// Offensive switchin decisions are based on which whether switchin moves first and whether it can win a 1v1
|
||||
isSwitchinFirst = AI_WhoStrikesFirstPartyMon(battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, aiMove);
|
||||
canSwitchinWin1v1 = CanSwitchinWin1v1(hitsToKOAI, GetNoOfHitsToKOBattlerDmg(damageDealt, opposingBattler), isSwitchinFirst, isFreeSwitch);
|
||||
|
||||
// Check for mon with resistance and super effective move for GetBestMonTypeMatchup
|
||||
// Check for Baton Pass; hitsToKO requirements mean mon can boost and BP without dying whether it's slower or not
|
||||
if (aiMove == MOVE_BATON_PASS)
|
||||
{
|
||||
if ((isSwitchinFirst && hitsToKOAI > 1) || hitsToKOAI > 2) // Need to take an extra hit if slower
|
||||
bits |= 1u << i;
|
||||
}
|
||||
|
||||
// Check that good type matchups get at least two turns and set best type matchup mon
|
||||
if (typeMatchup < bestResist)
|
||||
{
|
||||
if (canSwitchinWin1v1)
|
||||
{
|
||||
bestResist = typeMatchup;
|
||||
typeMatchupId = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for mon with resistance and super effective move for best type matchup mon with effective move
|
||||
if (aiMove != MOVE_NONE && gMovesInfo[aiMove].power != 0)
|
||||
{
|
||||
if (typeMatchup < bestResistEffective)
|
||||
{
|
||||
if (AI_GetTypeEffectiveness(aiMove, battler, opposingBattler) >= UQ_4_12(2.0))
|
||||
{
|
||||
// Assuming a super effective move would do significant damage or scare the player out, so not being as conservative here
|
||||
if (hitsToKOAI > hitsToKOAIThreshold)
|
||||
if (canSwitchinWin1v1)
|
||||
{
|
||||
bestResistEffective = typeMatchup;
|
||||
typeMatchupEffectiveId = i;
|
||||
|
@ -1905,10 +1936,10 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
if (gMovesInfo[aiMove].effect == EFFECT_EXPLOSION && damageDealt < playerMonHP)
|
||||
continue;
|
||||
|
||||
// Check that mon isn't one shot and set GetBestMonDmg if applicable
|
||||
// Check that mon isn't one shot and set best damage mon
|
||||
if (damageDealt > maxDamageDealt)
|
||||
{
|
||||
if(hitsToKOAI > hitsToKOAIThreshold)
|
||||
if((isFreeSwitch && hitsToKOAI > 1) || hitsToKOAI > 2) // This is a "default", we have uniquely low standards
|
||||
{
|
||||
maxDamageDealt = damageDealt;
|
||||
damageMonId = i;
|
||||
|
@ -1919,74 +1950,40 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
// If AI mon can one shot
|
||||
if (damageDealt > playerMonHP)
|
||||
{
|
||||
// If AI mon outspeeds and doesn't die to hazards
|
||||
if ((((aiMonSpeed > playerMonSpeed && !(gFieldStatuses & STATUS_FIELD_TRICK_ROOM)) || aiMovePriority > 0) // Outspeed if not Trick Room
|
||||
|| ((gFieldStatuses & STATUS_FIELD_TRICK_ROOM) // Trick Room
|
||||
&& (aiMonSpeed < playerMonSpeed || (ItemId_GetHoldEffect(AI_DATA->switchinCandidate.battleMon.item) == HOLD_EFFECT_ROOM_SERVICE && aiMonSpeed * 2 / 3 < playerMonSpeed)))) // Trick Room speeds
|
||||
&& AI_DATA->switchinCandidate.battleMon.hp > GetSwitchinHazardsDamage(battler, &AI_DATA->switchinCandidate.battleMon)) // Hazards
|
||||
if (canSwitchinWin1v1)
|
||||
{
|
||||
// We have a revenge killer
|
||||
if (isSwitchinFirst)
|
||||
revengeKillerId = i;
|
||||
}
|
||||
|
||||
// If AI mon is outsped
|
||||
else
|
||||
{
|
||||
// If AI mon can't be OHKO'd
|
||||
if (hitsToKOAI > hitsToKOAIThreshold)
|
||||
{
|
||||
// We have a slow revenge killer
|
||||
slowRevengeKillerId = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If AI mon can two shot
|
||||
if (damageDealt > playerMonHP / 2)
|
||||
{
|
||||
// If AI mon outspeeds
|
||||
if (((aiMonSpeed > playerMonSpeed && !(gFieldStatuses & STATUS_FIELD_TRICK_ROOM)) || aiMovePriority > 0) // Outspeed if not Trick Room
|
||||
|| (((gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && gFieldTimers.trickRoomTimer > 1) // Trick Room has at least 2 turns left
|
||||
&& (aiMonSpeed < playerMonSpeed || (ItemId_GetHoldEffect(AI_DATA->switchinCandidate.battleMon.item) == HOLD_EFFECT_ROOM_SERVICE && aiMonSpeed * 2/ 3 < playerMonSpeed)))) // Trick Room speeds
|
||||
if (canSwitchinWin1v1)
|
||||
{
|
||||
// If AI mon can't be OHKO'd
|
||||
if (hitsToKOAI > hitsToKOAIThreshold)
|
||||
{
|
||||
// We have a fast threaten
|
||||
if (isSwitchinFirst)
|
||||
fastThreatenId = i;
|
||||
}
|
||||
}
|
||||
// If AI mon is outsped
|
||||
else
|
||||
{
|
||||
// If AI mon can't be 2HKO'd
|
||||
if (hitsToKOAI > hitsToKOAIThreshold + 1)
|
||||
{
|
||||
// We have a slow threaten
|
||||
slowThreatenId = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If mon can trap
|
||||
if (CanAbilityTrapOpponent(AI_DATA->switchinCandidate.battleMon.ability, opposingBattler))
|
||||
{
|
||||
hitsToKOPlayer = GetNoOfHitsToKOBattlerDmg(damageDealt, opposingBattler);
|
||||
if (CountUsablePartyMons(opposingBattler) > 0
|
||||
&& (((hitsToKOAI > hitsToKOPlayer && isSwitchAfterKO) // If can 1v1 after a KO
|
||||
|| (hitsToKOAI == hitsToKOPlayer && isSwitchAfterKO && (aiMonSpeed > playerMonSpeed || aiMovePriority > 0)))
|
||||
|| ((hitsToKOAI > hitsToKOPlayer + 1 && !isSwitchAfterKO) // If can 1v1 after mid battle
|
||||
|| (hitsToKOAI == hitsToKOPlayer + 1 && !isSwitchAfterKO && (aiMonSpeed > playerMonSpeed || aiMovePriority > 0)))))
|
||||
if (CanAbilityTrapOpponent(AI_DATA->switchinCandidate.battleMon.ability, opposingBattler)
|
||||
&& CountUsablePartyMons(opposingBattler) > 0
|
||||
&& canSwitchinWin1v1)
|
||||
trapperId = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
batonPassId = GetRandomSwitchinWithBatonPass(aliveCount, bits, firstId, lastId, i);
|
||||
|
||||
// Different switching priorities depending on switching mid battle vs switching after a KO
|
||||
if (isSwitchAfterKO)
|
||||
// Different switching priorities depending on switching mid battle vs switching after a KO or slow switch
|
||||
if (isFreeSwitch)
|
||||
{
|
||||
// Return Trapper > Revenge Killer > Type Matchup > Baton Pass > Best Damage
|
||||
if (trapperId != PARTY_SIZE) return trapperId;
|
||||
|
@ -2009,8 +2006,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
else if (batonPassId != PARTY_SIZE) return batonPassId;
|
||||
}
|
||||
// If ace mon is the last available Pokemon and U-Turn/Volt Switch was used - switch to the mon.
|
||||
if (aceMonId != PARTY_SIZE
|
||||
&& (gMovesInfo[gLastUsedMove].effect == EFFECT_HIT_ESCAPE || gMovesInfo[gLastUsedMove].effect == EFFECT_PARTING_SHOT || gMovesInfo[gLastUsedMove].effect == EFFECT_BATON_PASS || gMovesInfo[gLastUsedMove].effect == EFFECT_CHILLY_RECEPTION || gMovesInfo[gLastUsedMove].effect == EFFECT_SHED_TAIL))
|
||||
if (aceMonId != PARTY_SIZE && IsSwitchOutEffect(gMovesInfo[gLastUsedMove].effect))
|
||||
return aceMonId;
|
||||
|
||||
return PARTY_SIZE;
|
||||
|
@ -2082,7 +2078,6 @@ u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd)
|
|||
return bestMonId;
|
||||
}
|
||||
|
||||
// Split ideal mon decision between after previous mon KO'd (prioritize offensive options) and after switching active mon out (prioritize defensive options), and expand the scope of both.
|
||||
// Only use better mon selection if AI_FLAG_SMART_MON_CHOICES is set for the trainer.
|
||||
if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_MON_CHOICES && !IsDoubleBattle()) // Double Battles aren't included in AI_FLAG_SMART_MON_CHOICE. Defaults to regular switch in logic
|
||||
{
|
||||
|
@ -2103,11 +2098,11 @@ u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd)
|
|||
|| gBattlerPartyIndexes[battlerIn2] == i
|
||||
|| i == gBattleStruct->monToSwitchIntoId[battlerIn1]
|
||||
|| i == gBattleStruct->monToSwitchIntoId[battlerIn2]
|
||||
|| (GetMonAbility(&party[i]) == ABILITY_TRUANT && IsTruantMonVulnerable(battler, opposingBattler))) // While not really invalid per se, not really wise to switch into this mon.)
|
||||
|| (GetMonAbility(&party[i]) == ABILITY_TRUANT && IsTruantMonVulnerable(battler, opposingBattler))) // While not really invalid per se, not really wise to switch into this mon.
|
||||
{
|
||||
invalidMons |= 1u << i;
|
||||
}
|
||||
else if (IsAceMon(battler, i))// Save Ace Pokemon for last.
|
||||
else if (IsAceMon(battler, i)) // Save Ace Pokemon for last.
|
||||
{
|
||||
aceMonId = i;
|
||||
invalidMons |= 1u << i;
|
||||
|
|
|
@ -2375,6 +2375,22 @@ bool32 IsSelfStatLoweringEffect(u32 effect)
|
|||
}
|
||||
}
|
||||
|
||||
bool32 IsSwitchOutEffect(u32 effect)
|
||||
{
|
||||
// Switch out effects like U-Turn, Volt Switch, etc.
|
||||
switch (effect)
|
||||
{
|
||||
case EFFECT_HIT_ESCAPE:
|
||||
case EFFECT_PARTING_SHOT:
|
||||
case EFFECT_BATON_PASS:
|
||||
case EFFECT_CHILLY_RECEPTION:
|
||||
case EFFECT_SHED_TAIL:
|
||||
return TRUE;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
bool32 HasDamagingMove(u32 battlerId)
|
||||
{
|
||||
u32 i;
|
||||
|
@ -3492,14 +3508,14 @@ s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct Battl
|
|||
{
|
||||
gBattleMons[battlerAtk] = switchinCandidate;
|
||||
AI_THINKING_STRUCT->saved[battlerDef].saved = TRUE;
|
||||
SetBattlerData(battlerDef); // set known opposing battler data
|
||||
SetBattlerAiData(battlerDef, AI_DATA); // set known opposing battler data
|
||||
AI_THINKING_STRUCT->saved[battlerDef].saved = FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
gBattleMons[battlerDef] = switchinCandidate;
|
||||
AI_THINKING_STRUCT->saved[battlerAtk].saved = TRUE;
|
||||
SetBattlerData(battlerAtk); // set known opposing battler data
|
||||
SetBattlerAiData(battlerAtk, AI_DATA); // set known opposing battler data
|
||||
AI_THINKING_STRUCT->saved[battlerAtk].saved = FALSE;
|
||||
}
|
||||
|
||||
|
@ -3509,6 +3525,18 @@ s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct Battl
|
|||
return dmg.expected;
|
||||
}
|
||||
|
||||
u32 AI_WhoStrikesFirstPartyMon(u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 moveConsidered)
|
||||
{
|
||||
struct BattlePokemon *savedBattleMons = AllocSaveBattleMons();
|
||||
gBattleMons[battlerAtk] = switchinCandidate;
|
||||
|
||||
SetBattlerAiData(battlerAtk, AI_DATA);
|
||||
u32 aiMonFaster = AI_IsFaster(battlerAtk, battlerDef, moveConsidered);
|
||||
FreeRestoreBattleMons(savedBattleMons);
|
||||
|
||||
return aiMonFaster;
|
||||
}
|
||||
|
||||
s32 CountUsablePartyMons(u32 battlerId)
|
||||
{
|
||||
s32 battlerOnField1, battlerOnField2, i, ret;
|
||||
|
|
|
@ -3239,6 +3239,10 @@ void SwitchInClearSetData(u32 battler)
|
|||
gSpecialStatuses[battler].specialDmg = 0;
|
||||
gBattleStruct->enduredDamage &= ~(1u << battler);
|
||||
|
||||
// Reset Eject Button / Eject Pack switch detection
|
||||
AI_DATA->ejectButtonSwitch = FALSE;
|
||||
AI_DATA->ejectPackSwitch = FALSE;
|
||||
|
||||
// Reset G-Max Chi Strike boosts.
|
||||
gBattleStruct->bonusCritStages[battler] = 0;
|
||||
|
||||
|
|
|
@ -6296,10 +6296,12 @@ static void Cmd_moveend(void)
|
|||
if (ejectButtonBattlers & (1u << battler))
|
||||
{
|
||||
gBattlescriptCurrInstr = BattleScript_EjectButtonActivates;
|
||||
AI_DATA->ejectButtonSwitch = TRUE;
|
||||
}
|
||||
else // Eject Pack
|
||||
{
|
||||
gBattlescriptCurrInstr = BattleScript_EjectPackActivates;
|
||||
AI_DATA->ejectPackSwitch = TRUE;
|
||||
// Are these 2 lines below needed?
|
||||
gProtectStructs[battler].statFell = FALSE;
|
||||
gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage = TRUE;
|
||||
|
|
|
@ -215,23 +215,97 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize
|
|||
{
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES);
|
||||
PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); }
|
||||
PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); SpAttack(50); }
|
||||
OPPONENT(SPECIES_PONYTA) { Level(1); Moves(MOVE_NONE); Speed(4); } // Forces switchout
|
||||
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_HEADBUTT); Speed(4); SpDefense(41); } // Mid battle, AI sends out Aron
|
||||
OPPONENT(SPECIES_ELECTRODE) { Level(30); Moves(MOVE_CHARGE_BEAM); Speed(6); }
|
||||
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); } // Mid battle, AI sends out Aron
|
||||
OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53);}
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SWITCH(opponent, 1); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize offensive options after slow U-Turn")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(gMovesInfo[MOVE_FALSE_SWIPE].effect == EFFECT_FALSE_SWIPE);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES);
|
||||
PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_FALSE_SWIPE, MOVE_BOOMBURST); Speed(5); SpAttack(50); }
|
||||
OPPONENT(SPECIES_PONYTA) { Level(1); Moves(MOVE_U_TURN); Speed(4); } // Forces switchout
|
||||
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); }
|
||||
OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_FALSE_SWIPE); EXPECT_SEND_OUT(opponent, 2); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize offensive options after Eject Button")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(gItemsInfo[ITEM_EJECT_BUTTON].holdEffect == HOLD_EFFECT_EJECT_BUTTON);
|
||||
ASSUME(gMovesInfo[MOVE_FALSE_SWIPE].effect == EFFECT_FALSE_SWIPE);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES);
|
||||
PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_FALSE_SWIPE, MOVE_BOOMBURST); Speed(5); SpAttack(50); }
|
||||
OPPONENT(SPECIES_PONYTA) { Level(1); Item(ITEM_EJECT_BUTTON); Moves(MOVE_TACKLE); Speed(4); } // Forces switchout
|
||||
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); }
|
||||
OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_FALSE_SWIPE); EXPECT_SEND_OUT(opponent, 2); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize offensive options after Eject Pack")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(gItemsInfo[ITEM_EJECT_PACK].holdEffect == HOLD_EFFECT_EJECT_PACK);
|
||||
ASSUME(gMovesInfo[MOVE_GROWL].effect == EFFECT_ATTACK_DOWN);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES);
|
||||
PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_GROWL, MOVE_BOOMBURST); Speed(5); SpAttack(50); }
|
||||
OPPONENT(SPECIES_PONYTA) { Level(1); Item(ITEM_EJECT_PACK); Moves(MOVE_TACKLE); Speed(4); } // Forces switchout
|
||||
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); }
|
||||
OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_GROWL); EXPECT_SEND_OUT(opponent, 2); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize defensive options after Eject Pack if mon outspeeds")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(gItemsInfo[ITEM_EJECT_PACK].holdEffect == HOLD_EFFECT_EJECT_PACK);
|
||||
ASSUME(MoveHasAdditionalEffectSelf(MOVE_OVERHEAT, MOVE_EFFECT_SP_ATK_MINUS_2) == TRUE);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES);
|
||||
PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); SpAttack(50); }
|
||||
OPPONENT(SPECIES_PONYTA) { Level(1); Item(ITEM_EJECT_PACK); Moves(MOVE_OVERHEAT); Speed(6); } // Forces switchout
|
||||
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); }
|
||||
OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SEND_OUT(opponent, 1); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize offensive options after Eject Pack if mon outspeeds but was Intimidate'd")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(gItemsInfo[ITEM_EJECT_PACK].holdEffect == HOLD_EFFECT_EJECT_PACK);
|
||||
ASSUME(MoveHasAdditionalEffectSelf(MOVE_OVERHEAT, MOVE_EFFECT_SP_ATK_MINUS_2) == TRUE);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES);
|
||||
PLAYER(SPECIES_STARAPTOR) { Level(30); Ability(ABILITY_INTIMIDATE); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); SpAttack(50); }
|
||||
OPPONENT(SPECIES_PONYTA) { Level(1); Item(ITEM_EJECT_PACK); Moves(MOVE_OVERHEAT); Speed(6); } // Forces switchout
|
||||
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); }
|
||||
OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SWITCH(opponent, 2); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Post-KO switches prioritize offensive options")
|
||||
{
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES);
|
||||
PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); }
|
||||
OPPONENT(SPECIES_PONYTA) { Level(1); Moves(MOVE_TACKLE); Speed(4); }
|
||||
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_HEADBUTT); Speed(4); } // Mid battle, AI sends out Aron
|
||||
OPPONENT(SPECIES_ELECTRODE) { Level(30); Moves(MOVE_CHARGE_BEAM); Speed(6); }
|
||||
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); } // Mid battle, AI sends out Aron
|
||||
OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SEND_OUT(opponent, 2); }
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue