Should switch refactor to facilitate switch prediction (#5466)

Co-authored-by: Martin Griffin <martinrgriffin@gmail.com>
This commit is contained in:
Pawkkie 2024-10-25 07:04:42 -04:00 committed by GitHub
parent afa7ab9b2e
commit 7413d107ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 589 additions and 462 deletions

View file

@ -361,7 +361,7 @@ struct AiLogicData
u16 items[MAX_BATTLERS_COUNT];
u16 holdEffects[MAX_BATTLERS_COUNT];
u8 holdEffectParams[MAX_BATTLERS_COUNT];
u16 predictedMoves[MAX_BATTLERS_COUNT];
u16 lastUsedMove[MAX_BATTLERS_COUNT];
u8 hpPercents[MAX_BATTLERS_COUNT];
u16 partnerMove;
u16 speedStats[MAX_BATTLERS_COUNT]; // Speed stats for all battles, calculated only once, same way as damages

View file

@ -95,7 +95,6 @@ typedef s32 (*AiScoreFunc)(u32, u32, u32, s32);
return score; \
}
u32 ComputeBattleAiScores(u32 battler);
void BattleAI_SetupItems(void);
void BattleAI_SetupFlags(void);
void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler);

View file

@ -4,7 +4,7 @@
void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId);
void AI_TrySwitchOrUseItem(u32 battler);
u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd);
bool32 ShouldSwitch(u32 battler, bool32 emitResult);
bool32 ShouldSwitch(u32 battler);
bool32 IsMonGrounded(u16 heldItemEffect, u32 ability, u8 type1, u8 type2);
#endif // GUARD_BATTLE_AI_SWITCH_ITEMS_H

View file

@ -162,11 +162,16 @@ enum RandomTag
RNG_FICKLE_BEAM,
RNG_AI_ABILITY,
RNG_AI_SWITCH_HASBADODDS,
RNG_AI_SWITCH_WONDER_GUARD,
RNG_AI_SWITCH_BADLY_POISONED,
RNG_AI_SWITCH_CURSED,
RNG_AI_SWITCH_NIGHTMARE,
RNG_AI_SWITCH_SEEDED,
RNG_AI_SWITCH_ABSORBING,
RNG_AI_SWITCH_NATURAL_CURE,
RNG_AI_SWITCH_REGENERATOR,
RNG_AI_SWITCH_ENCORE,
RNG_AI_SWITCH_STATS_LOWERED,
RNG_AI_SWITCH_SE_DEFENSIVE,
RNG_SHELL_SIDE_ARM,
RNG_RANDOM_TARGET,
RNG_HEALER,

View file

@ -232,7 +232,6 @@ void BattleAI_SetupFlags(void)
}
}
// sBattler_AI set in ComputeBattleAiScores
void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler)
{
s32 i;
@ -287,14 +286,6 @@ u32 BattleAI_ChooseMoveOrAction(void)
return ret;
}
// damages/other info computed in GetAIDataAndCalcDmg
u32 ComputeBattleAiScores(u32 battler)
{
sBattler_AI = battler;
BattleAI_SetupAIData(0xF, sBattler_AI);
return BattleAI_ChooseMoveOrAction();
}
static void CopyBattlerDataToAIParty(u32 bPosition, u32 side)
{
u32 battler = GetBattlerAtPosition(bPosition);
@ -397,7 +388,7 @@ void SetBattlerAiData(u32 battler, struct AiLogicData *aiData)
aiData->items[battler] = gBattleMons[battler].item;
holdEffect = aiData->holdEffects[battler] = AI_DecideHoldEffectForTurn(battler);
aiData->holdEffectParams[battler] = GetBattlerHoldEffectParam(battler);
aiData->predictedMoves[battler] = gLastMoves[battler];
aiData->lastUsedMove[battler] = gLastMoves[battler];
aiData->hpPercents[battler] = GetHealthPercentage(battler);
aiData->moveLimitations[battler] = CheckMoveLimitations(battler, 0, MOVE_LIMITATIONS_ALL);
aiData->speedStats[battler] = GetBattlerTotalSpeedStatArgs(battler, ability, holdEffect);
@ -729,7 +720,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk);
u32 i;
u32 weather;
u32 predictedMove = aiData->predictedMoves[battlerDef];
u32 predictedMove = aiData->lastUsedMove[battlerDef];
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
return score;
@ -2661,7 +2652,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
bool32 partnerProtecting = (gMovesInfo[aiData->partnerMove].effect == EFFECT_PROTECT);
bool32 attackerHasBadAbility = (gAbilitiesInfo[aiData->abilities[battlerAtk]].aiRating < 0);
bool32 partnerHasBadAbility = (gAbilitiesInfo[atkPartnerAbility].aiRating < 0);
u32 predictedMove = aiData->predictedMoves[battlerDef];
u32 predictedMove = aiData->lastUsedMove[battlerDef];
SetTypeBeforeUsingMove(move, battlerAtk);
moveType = GetMoveType(move);
@ -3195,7 +3186,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
u32 effectiveness = aiData->effectiveness[battlerAtk][battlerDef][movesetIndex];
s32 score = 0;
u32 predictedMove = aiData->predictedMoves[battlerDef];
u32 predictedMove = aiData->lastUsedMove[battlerDef];
u32 predictedMoveSlot = GetMoveSlot(GetMovesArray(battlerDef), predictedMove);
bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk);
u32 i;
@ -4369,7 +4360,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
break;
case EFFECT_MAGNET_RISE:
if (IsBattlerGrounded(battlerAtk) && HasDamagingMoveOfType(battlerDef, TYPE_ELECTRIC)
&& !(AI_GetTypeEffectiveness(MOVE_EARTHQUAKE, battlerDef, battlerAtk) == AI_EFFECTIVENESS_x0)) // Doesn't resist ground move
&& !(AI_GetMoveEffectiveness(MOVE_EARTHQUAKE, battlerDef, battlerAtk) == AI_EFFECTIVENESS_x0)) // Doesn't resist ground move
{
if (AI_IsFaster(battlerAtk, battlerDef, move)) // Attacker goes first
{
@ -4387,7 +4378,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
break;
case EFFECT_CAMOUFLAGE:
if (predictedMove != MOVE_NONE && AI_IsFaster(battlerAtk, battlerDef, move) // Attacker goes first
&& !IS_MOVE_STATUS(move) && AI_GetTypeEffectiveness(predictedMove, battlerDef, battlerAtk) != AI_EFFECTIVENESS_x0)
&& !IS_MOVE_STATUS(move) && AI_GetMoveEffectiveness(predictedMove, battlerDef, battlerAtk) != AI_EFFECTIVENESS_x0)
ADJUST_SCORE(DECENT_EFFECT);
break;
case EFFECT_TOXIC_THREAD:

File diff suppressed because it is too large Load diff

View file

@ -3070,10 +3070,7 @@ bool32 AnyPartyMemberStatused(u32 battlerId, bool32 checkSoundproof)
struct Pokemon *party;
u32 i, battlerOnField1, battlerOnField2;
if (GetBattlerSide(battlerId) == B_SIDE_PLAYER)
party = gPlayerParty;
else
party = gEnemyParty;
party = GetBattlerParty(battlerId);
if (IsDoubleBattle())
{
@ -3361,11 +3358,7 @@ bool32 ShouldUseWishAromatherapy(u32 battlerAtk, u32 battlerDef, u32 move)
bool32 needHealing = FALSE;
GetAIPartyIndexes(battlerAtk, &firstId, &lastId);
if (GetBattlerSide(battlerAtk) == B_SIDE_PLAYER)
party = gPlayerParty;
else
party = gEnemyParty;
party = GetBattlerParty(battlerAtk);
if (CountUsablePartyMons(battlerAtk) == 0
&& (CanTargetFaintAi(battlerDef, battlerAtk) || BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->abilities[battlerAtk])))
@ -3440,20 +3433,27 @@ s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct Battl
{
gBattleMons[battlerAtk] = switchinCandidate;
AI_THINKING_STRUCT->saved[battlerDef].saved = TRUE;
SetBattlerAiData(battlerDef, AI_DATA); // set known opposing battler data
SetBattlerAiData(battlerAtk, 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;
SetBattlerAiData(battlerAtk, AI_DATA); // set known opposing battler data
SetBattlerAiData(battlerDef, AI_DATA); // set known opposing battler data
AI_THINKING_STRUCT->saved[battlerAtk].saved = FALSE;
}
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE, AI_GetWeather(AI_DATA), rollType);
// restores original gBattleMon struct
FreeRestoreBattleMons(savedBattleMons);
if (isPartyMonAttacker)
SetBattlerAiData(battlerAtk, AI_DATA);
else
SetBattlerAiData(battlerDef, AI_DATA);
return dmg.expected;
}
@ -3473,11 +3473,7 @@ s32 CountUsablePartyMons(u32 battlerId)
{
s32 battlerOnField1, battlerOnField2, i, ret;
struct Pokemon *party;
if (GetBattlerSide(battlerId) == B_SIDE_PLAYER)
party = gPlayerParty;
else
party = gEnemyParty;
party = GetBattlerParty(battlerId);
if (IsDoubleBattle())
{
@ -3509,11 +3505,7 @@ bool32 IsPartyFullyHealedExceptBattler(u32 battlerId)
{
struct Pokemon *party;
u32 i;
if (GetBattlerSide(battlerId) == B_SIDE_PLAYER)
party = gPlayerParty;
else
party = gEnemyParty;
party = GetBattlerParty(battlerId);
for (i = 0; i < PARTY_SIZE; i++)
{

View file

@ -4209,20 +4209,25 @@ static void HandleTurnActionSelectionState(void)
case STATE_TURN_START_RECORD: // Recorded battle related action on start of every turn.
RecordedBattle_CopyBattlerMoves(battler);
gBattleCommunication[battler] = STATE_BEFORE_ACTION_CHOSEN;
u32 isAiRisky = AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_RISKY; // Risky AI switches aggressively even mid battle
// Do AI score computations here so we can use them in AI_TrySwitchOrUseItem
if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart())
&& (BattlerHasAi(battler) && !(gBattleTypeFlags & BATTLE_TYPE_PALACE)))
{
AI_DATA->aiCalcInProgress = TRUE;
if (ShouldSwitch(battler, FALSE))
AI_DATA->shouldSwitch |= (1u << battler);
if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_RISKY) // Risky AI switches aggressively even mid battle
AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, TRUE);
else
AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, FALSE);
gBattleStruct->aiMoveOrAction[battler] = ComputeBattleAiScores(battler);
// Setup battler data
sBattler_AI = battler;
BattleAI_SetupAIData(0xF, sBattler_AI);
// Setup switching data
AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, isAiRisky);
if (ShouldSwitch(battler))
AI_DATA->shouldSwitch |= (1u << battler);
// Do scoring
gBattleStruct->aiMoveOrAction[battler] = BattleAI_ChooseMoveOrAction();
AI_DATA->aiCalcInProgress = FALSE;
}
// fallthrough

View file

@ -1353,11 +1353,7 @@ u8 GetBattlerMoveSlotId(u8 battlerId, u16 moveId)
{
s32 i;
struct Pokemon *party;
if (GetBattlerSide(battlerId) == B_SIDE_PLAYER)
party = gPlayerParty;
else
party = gEnemyParty;
party = GetBattlerParty(battlerId);
i = 0;
while (1)

View file

@ -739,6 +739,7 @@ AI_SINGLE_BATTLE_TEST("AI calculates guaranteed criticals and detects critical i
AI_DOUBLE_BATTLE_TEST("AI recognizes Volt Absorb received from Trace")
{
KNOWN_FAILING; // MGriffin's PR that switched two turn charging moves in AI tests broke this test, waiting on a fix
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_MAGNETON);

View file

@ -68,7 +68,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: Mid-battle switches prioritize offensive o
PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); }
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_ELECTRODE) { Level(30); Moves(MOVE_CHARGE_BEAM); Speed(6); Ability(ABILITY_STATIC); }
} WHEN {
TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SWITCH(opponent, aiRiskyFlag? 2 : 1); }
}

View file

@ -473,12 +473,14 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if mon would
}
}
AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if it can't deal damage to a mon with Wonder Guard 66% of the time")
AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if it can't deal damage to a mon with Wonder Guard")
{
PASSES_RANDOMLY(66, 100, RNG_AI_SWITCH_WONDER_GUARD);
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_SHEDINJA].types[0] == TYPE_BUG);
ASSUME(gSpeciesInfo[SPECIES_SHEDINJA].types[1] == TYPE_GHOST);
ASSUME(gSpeciesInfo[SPECIES_SHEDINJA].abilities[0] == ABILITY_WONDER_GUARD);
ASSUME(gSpeciesInfo[SPECIES_SHEDINJA].abilities[1] == ABILITY_NONE);
ASSUME(gSpeciesInfo[SPECIES_SHEDINJA].abilities[2] == ABILITY_NONE);
ASSUME(gMovesInfo[MOVE_TACKLE].type == TYPE_NORMAL);
ASSUME(gMovesInfo[MOVE_SHADOW_BALL].type == TYPE_GHOST);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
@ -490,9 +492,8 @@ AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if it can't deal damage to
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it can't deal damage to a mon with Wonder Guard 100% of the time")
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it can't deal damage to a mon with Wonder Guard")
{
PASSES_RANDOMLY(100, 100, RNG_AI_SWITCH_WONDER_GUARD);
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_SHEDINJA].types[0] == TYPE_BUG);
ASSUME(gSpeciesInfo[SPECIES_SHEDINJA].types[1] == TYPE_GHOST);
@ -510,19 +511,22 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it can't d
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been Toxic'd for at least two turns 50% of the time with more than 1/3 HP remaining")
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been Toxic'd for at least two turns 50% of the time with more than 1/3 HP remaining with good switchin")
{
PASSES_RANDOMLY(50, 100, RNG_AI_SWITCH_BADLY_POISONED);
u32 species = SPECIES_NONE, odds = 0;
PARAMETRIZE { species = SPECIES_ZIGZAGOON, odds = 0; }
PARAMETRIZE { species = SPECIES_HARIYAMA, odds = 50; }
PASSES_RANDOMLY(odds, 100, RNG_AI_SWITCH_BADLY_POISONED);
GIVEN {
ASSUME(gMovesInfo[MOVE_TOXIC].effect == EFFECT_TOXIC);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE, MOVE_CELEBRATE, MOVE_TOXIC); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE, MOVE_CELEBRATE, MOVE_TOXIC, MOVE_AURA_SPHERE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(species) { Moves(MOVE_ROCK_SMASH); }
} WHEN {
TURN { MOVE(player, MOVE_TOXIC); EXPECT_MOVE(opponent, MOVE_TACKLE); }
TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_TACKLE); }
TURN { MOVE(player, MOVE_TACKLE); EXPECT_SWITCH(opponent, 1); }
TURN { MOVE(player, MOVE_AURA_SPHERE); EXPECT_MOVE(opponent, MOVE_TACKLE); }
TURN { MOVE(player, MOVE_AURA_SPHERE); EXPECT_SWITCH(opponent, 1); }
}
}
@ -571,3 +575,322 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has bee
TURN { MOVE(player, MOVE_LEECH_SEED); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been infatuated")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_ATTRACT].effect == EFFECT_ATTRACT);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_LUVDISC) { Moves(MOVE_ATTRACT); Gender(MON_FEMALE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); Gender(MON_MALE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); Gender(MON_MALE); }
} WHEN {
TURN { MOVE(player, MOVE_ATTRACT) ; EXPECT_MOVE(opponent, MOVE_TACKLE); }
TURN { MOVE(player, MOVE_ATTRACT) ; EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been Yawn'd with more than 1/3 HP remaining")
{
u32 hp;
PARAMETRIZE { hp = 30; }
PARAMETRIZE { hp = 10; }
GIVEN {
ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_SLAKOTH) { Moves(MOVE_TACKLE, MOVE_YAWN); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); HP(hp); MaxHP(30); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
} WHEN {
TURN { MOVE(player, MOVE_YAWN) ; EXPECT_MOVE(opponent, MOVE_TACKLE); }
if (hp == 30)
TURN { MOVE(player, MOVE_YAWN) ; EXPECT_SWITCH(opponent, 1); }
else
TURN { MOVE(player, MOVE_YAWN) ; EXPECT_MOVE(opponent, MOVE_TACKLE); }
}
}
AI_DOUBLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been Yawn'd with more than 1/3 HP remaining (Doubles)")
{
u32 hp;
PARAMETRIZE { hp = 30; }
PARAMETRIZE { hp = 10; }
GIVEN {
ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_SLAKOTH) { Moves(MOVE_TACKLE, MOVE_CELEBRATE, MOVE_YAWN); }
PLAYER(SPECIES_SLAKOTH) { Moves(MOVE_TACKLE, MOVE_CELEBRATE, MOVE_YAWN); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); HP(hp); MaxHP(30); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_YAWN, target: opponentLeft); MOVE(playerRight, MOVE_CELEBRATE, target: opponentLeft); }
if (hp == 30)
TURN { MOVE(playerLeft, MOVE_YAWN, target: opponentLeft); MOVE(playerRight, MOVE_CELEBRATE, target: opponentLeft); EXPECT_SWITCH(opponentLeft, 2); }
else
TURN { MOVE(playerLeft, MOVE_YAWN, target: opponentLeft); MOVE(playerRight, MOVE_CELEBRATE, target: opponentLeft); EXPECT_MOVE(opponentLeft, MOVE_TACKLE); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if player's mon is semi-invulnerable and it has an absorber")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_DIVE].type == TYPE_WATER);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_LUVDISC) { Moves(MOVE_DIVE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_MANTINE) { Moves(MOVE_TACKLE); Ability(ABILITY_WATER_ABSORB); }
} WHEN {
TURN { MOVE(player, MOVE_DIVE) ; EXPECT_MOVE(opponent, MOVE_TACKLE); }
TURN { SKIP_TURN(player); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has an absorber but current mon has SE move 33% of the time")
{
PASSES_RANDOMLY(33, 100, RNG_AI_SWITCH_ABSORBING);
GIVEN {
ASSUME(gMovesInfo[MOVE_WATER_GUN].type == TYPE_WATER);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_LUVDISC) { Moves(MOVE_WATER_GUN); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SHOCK_WAVE); }
OPPONENT(SPECIES_MANTINE) { Moves(MOVE_TACKLE); Ability(ABILITY_WATER_ABSORB); }
} WHEN {
TURN { MOVE(player, MOVE_WATER_GUN) ; EXPECT_MOVE(opponent, MOVE_SHOCK_WAVE); }
TURN { MOVE(player, MOVE_WATER_GUN) ; EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if player's mon is charging and it has an absorber")
{
PASSES_RANDOMLY(100, 100, RNG_AI_SWITCH_ABSORBING);
GIVEN {
ASSUME(gMovesInfo[MOVE_SOLAR_BEAM].type == TYPE_GRASS);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_BELLOSSOM) { Moves(MOVE_SOLAR_BEAM); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_AZUMARILL) { Moves(MOVE_PLAY_ROUGH); Ability(ABILITY_SAP_SIPPER); }
} WHEN {
TURN { MOVE(player, MOVE_SOLAR_BEAM) ; EXPECT_MOVE(opponent, MOVE_TACKLE); }
TURN { SKIP_TURN(player); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if player's mon is charging and it has a good switchin immunity (type)")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_DIG].type == TYPE_GROUND);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_SANDSHREW) { Moves(MOVE_DIG); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_SWELLOW) { Moves(MOVE_WING_ATTACK); }
} WHEN {
TURN { MOVE(player, MOVE_DIG) ; EXPECT_MOVE(opponent, MOVE_TACKLE); }
TURN { SKIP_TURN(player); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if player's mon is charging and it has a good switchin immunity (ability)")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_DIG].type == TYPE_GROUND);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_SANDSHREW) { Moves(MOVE_DIG); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_BRONZONG) { Moves(MOVE_PSYCHIC); Ability(ABILITY_LEVITATE); }
} WHEN {
TURN { MOVE(player, MOVE_DIG) ; EXPECT_MOVE(opponent, MOVE_TACKLE); }
TURN { SKIP_TURN(player); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has an absorber")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_WATER_GUN].type == TYPE_WATER);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_LUVDISC) { Moves(MOVE_WATER_GUN); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_MANTINE) { Moves(MOVE_TACKLE); Ability(ABILITY_WATER_ABSORB); }
} WHEN {
TURN { MOVE(player, MOVE_WATER_GUN) ; EXPECT_MOVE(opponent, MOVE_TACKLE); }
TURN { MOVE(player, MOVE_WATER_GUN) ; EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if opponent uses two-turn move and it has a switchin that wins 1v1")
{
u32 move;
PARAMETRIZE { move = MOVE_SKY_ATTACK; }
PARAMETRIZE { move = MOVE_FLY; }
GIVEN {
ASSUME(gMovesInfo[MOVE_FLY].effect == EFFECT_SEMI_INVULNERABLE);
ASSUME(gMovesInfo[MOVE_SKY_ATTACK].effect == EFFECT_TWO_TURNS_ATTACK);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_SWELLOW) { Moves(move); }
OPPONENT(SPECIES_MILOTIC) { Moves(MOVE_SURF); }
OPPONENT(SPECIES_LAIRON) { Moves(MOVE_ROCK_SLIDE); }
} WHEN {
TURN { MOVE(player, move); EXPECT_MOVE(opponent, MOVE_SURF); }
TURN { SKIP_TURN(player); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if badly statused with >= 50% HP remaining and has Natural Cure and a good switchin 66% of the time")
{
PASSES_RANDOMLY(66, 100, RNG_AI_SWITCH_NATURAL_CURE);
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_ODDISH) { Moves(MOVE_TOXIC, MOVE_TACKLE); }
OPPONENT(SPECIES_SWABLU) { Ability(ABILITY_NATURAL_CURE); Moves(MOVE_TACKLE, MOVE_PECK); }
OPPONENT(SPECIES_SWABLU) { Ability(ABILITY_NATURAL_CURE); Moves(MOVE_TACKLE); }
} WHEN {
TURN { MOVE(player, MOVE_TOXIC); EXPECT_MOVE(opponent, MOVE_PECK); }
TURN { MOVE(player, MOVE_TACKLE); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if it has <= 66% HP remaining and has Regenerator and a good switchin 50% of the time")
{
PASSES_RANDOMLY(50, 100, RNG_AI_SWITCH_REGENERATOR);
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_SLOWBRO) { MaxHP(100); HP(65); Ability(ABILITY_REGENERATOR); Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_SLOWBRO) { Ability(ABILITY_REGENERATOR); Moves(MOVE_TACKLE); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been Encore'd into a status move")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_ENCORE].effect == EFFECT_ENCORE);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_AZURILL) { Moves(MOVE_TACKLE, MOVE_ENCORE); }
OPPONENT(SPECIES_ODDISH) { Moves(MOVE_TOXIC, MOVE_SWEET_SCENT, MOVE_INGRAIN, MOVE_TACKLE); }
OPPONENT(SPECIES_ARON) { Moves(MOVE_METAL_CLAW); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_TOXIC); MOVE(player, MOVE_ENCORE); }
TURN { MOVE(player, MOVE_ENCORE); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will stay in if Encore'd into super effective move")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_ENCORE].effect == EFFECT_ENCORE);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_AZURILL) { Moves(MOVE_TACKLE, MOVE_ENCORE); }
OPPONENT(SPECIES_ODDISH) { Moves(MOVE_ACID); }
OPPONENT(SPECIES_ARON) { Moves(MOVE_METAL_CLAW); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_ACID); MOVE(player, MOVE_ENCORE); }
TURN { EXPECT_MOVE(opponent, MOVE_ACID); MOVE(player, MOVE_TACKLE); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if Encore'd into neutral move with good switchin 50% of the time")
{
KNOWN_FAILING; // AI still switches even if ShouldSwitch is set to immediately return FALSE, something external seems to be triggering this
PASSES_RANDOMLY(50, 100, RNG_AI_SWITCH_ENCORE);
GIVEN {
ASSUME(gMovesInfo[MOVE_ENCORE].effect == EFFECT_ENCORE);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_AZURILL) { Moves(MOVE_TACKLE, MOVE_ENCORE); }
OPPONENT(SPECIES_ODDISH) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ARON) { Moves(MOVE_METAL_CLAW); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_ENCORE); }
TURN { MOVE(player, MOVE_TACKLE); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if mon has Truant and opponent has Protect")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_ARON) { Moves(MOVE_TACKLE, MOVE_PROTECT); }
OPPONENT(SPECIES_SLAKING) { Ability(ABILITY_TRUANT); Moves(MOVE_BRICK_BREAK); }
OPPONENT(SPECIES_ARON) { Moves(MOVE_TACKLE); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_BRICK_BREAK); MOVE(player, MOVE_PROTECT); }
TURN { EXPECT_SWITCH(opponent, 1); MOVE(player, MOVE_TACKLE); }
}
}
AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if mon has Truant and opponent has invulnerability move and is faster")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_SWELLOW) { Speed(5); Moves(MOVE_FLY); }
OPPONENT(SPECIES_SLAKING) { Speed(4); Ability(ABILITY_TRUANT); Moves(MOVE_ROCK_SLIDE); }
OPPONENT(SPECIES_ARON) { Speed(4); Moves(MOVE_TACKLE); }
} WHEN {
TURN { MOVE(player, MOVE_FLY); EXPECT_MOVE(opponent, MOVE_ROCK_SLIDE); }
TURN { SKIP_TURN(player); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if main attacking stat lowered by 2 stages with good switchin candidate 50% of the time")
{
u32 aiSpecies = SPECIES_NONE, aiMove = MOVE_NONE, move = MOVE_NONE;
PASSES_RANDOMLY(50, 100, RNG_AI_SWITCH_STATS_LOWERED);
PARAMETRIZE {move = MOVE_CHARM; aiSpecies = SPECIES_FLAREON; aiMove = MOVE_FIRE_FANG; };
PARAMETRIZE {move = MOVE_EERIE_IMPULSE; aiSpecies = SPECIES_ESPEON; aiMove = MOVE_CONFUSION; };
GIVEN {
ASSUME(gMovesInfo[MOVE_CHARM].effect == EFFECT_ATTACK_DOWN_2);
ASSUME(gMovesInfo[MOVE_EERIE_IMPULSE].effect == EFFECT_SPECIAL_ATTACK_DOWN_2);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_ARON) { Moves(move, MOVE_TACKLE); }
OPPONENT(aiSpecies) { Moves(aiMove); }
OPPONENT(SPECIES_MILOTIC) { Moves(MOVE_SURF); }
} WHEN {
TURN { MOVE(player, move); EXPECT_MOVE(opponent, aiMove); }
TURN { MOVE(player, MOVE_TACKLE); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if main attacking stat lowered by 3+ stages")
{
u32 aiSpecies = SPECIES_NONE, aiMove = MOVE_NONE, move = MOVE_NONE, move2 = MOVE_NONE;
PASSES_RANDOMLY(100, 100, RNG_AI_SWITCH_STATS_LOWERED);
PARAMETRIZE {move = MOVE_GROWL; move2 = MOVE_CHARM; aiSpecies = SPECIES_FLAREON; aiMove = MOVE_FIRE_FANG; };
PARAMETRIZE {move = MOVE_CONFIDE; move2 = MOVE_EERIE_IMPULSE; aiSpecies = SPECIES_ESPEON; aiMove = MOVE_STORED_POWER; };
GIVEN {
ASSUME(gMovesInfo[MOVE_CHARM].effect == EFFECT_ATTACK_DOWN_2);
ASSUME(gMovesInfo[MOVE_EERIE_IMPULSE].effect == EFFECT_SPECIAL_ATTACK_DOWN_2);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_ARON) { Moves(move, move2, MOVE_TACKLE); }
OPPONENT(aiSpecies) { Moves(aiMove); }
OPPONENT(SPECIES_MILOTIC) { Moves(MOVE_SURF); }
} WHEN {
TURN { MOVE(player, move); EXPECT_MOVE(opponent, aiMove); }
TURN { MOVE(player, move2); EXPECT_MOVE(opponent, aiMove); }
TURN { MOVE(player, MOVE_TACKLE); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch into mon with good type matchup and SE move if current mon has no SE move and no stats raised")
{
u32 odds = 0, species = SPECIES_NONE, move = MOVE_NONE;
PARAMETRIZE { odds = 33; species = SPECIES_SCIZOR; move = MOVE_X_SCISSOR; }
PARAMETRIZE { odds = 50; species = SPECIES_DUSCLOPS; move = MOVE_SHADOW_BALL; }
PASSES_RANDOMLY(odds, 100, RNG_AI_SWITCH_SE_DEFENSIVE);
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_MUNNA) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_MUNNA) { Moves(MOVE_TACKLE); }
OPPONENT(species) { Moves(move); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_TACKLE); }
TURN { MOVE(player, MOVE_TACKLE); EXPECT_SWITCH(opponent, 1); }
}
}

View file

@ -780,6 +780,8 @@ void TestRunner_Battle_CheckChosenMove(u32 battlerId, u32 moveId, u32 target)
if (!expectedAction->actionSet)
return;
DATA.trial.lastActionTurn = gBattleResults.battleTurnCounter;
if (!expectedAction->pass)
{
u32 i, expectedMoveId = 0, countExpected;
@ -849,6 +851,8 @@ void TestRunner_Battle_CheckSwitch(u32 battlerId, u32 partyIndex)
if (!expectedAction->actionSet)
return;
DATA.trial.lastActionTurn = gBattleResults.battleTurnCounter;
if (!expectedAction->pass)
{
if (expectedAction->type != B_ACTION_SWITCH)