Improve "all moves bad" switching (#6453)

This commit is contained in:
Pawkkie 2025-03-20 11:43:56 -04:00 committed by GitHub
parent 9a23f19575
commit 4c18e88282
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 33 deletions

View File

@ -44,6 +44,9 @@
#define SHOULD_SWITCH_REGENERATOR_PERCENTAGE 50
#define SHOULD_SWITCH_REGENERATOR_STATS_RAISED_PERCENTAGE 20
// AI switchin considerations
#define ALL_MOVES_BAD_STATUS_MOVES_BAD FALSE // If the AI has no moves that affect the target, ShouldSwitchIfAllMovesBad can prompt a switch. Enabling this config will ignore status moves that can affect the target when making this decision.
// AI held item-based move scoring
#define LOW_ACCURACY_THRESHOLD 75 // Moves with accuracy equal OR below this value are considered low accuracy

View File

@ -331,6 +331,38 @@ static bool32 ShouldSwitchIfTruant(u32 battler)
return FALSE;
}
static u32 FindMonWithMoveOfEffectiveness(u32 battler, u32 opposingBattler, uq4_12_t effectiveness)
{
u32 move, i, j;
s32 firstId;
s32 lastId; // + 1
struct Pokemon *party = NULL;
// Get party information.
GetAIPartyIndexes(battler, &firstId, &lastId);
party = GetBattlerParty(battler);
// Find a Pokémon in the party that has a super effective move.
for (i = firstId; i < lastId; i++)
{
if (!IsValidForBattle(&party[i]))
continue;
if (i == gBattlerPartyIndexes[battler])
continue;
if (IsAceMon(battler, i))
continue;
for (j = 0; j < MAX_MON_MOVES; j++)
{
move = GetMonData(&party[i], MON_DATA_MOVE1 + j);
if (move != MOVE_NONE && AI_GetMoveEffectiveness(move, battler, opposingBattler) >= effectiveness && gMovesInfo[move].power != 0)
return SetSwitchinAndSwitch(battler, i);
}
}
return FALSE; // There is not a single Pokémon in the party that has a move with this effectiveness threshold
}
static bool32 ShouldSwitchIfAllMovesBad(u32 battler)
{
u32 moveIndex;
@ -355,24 +387,27 @@ static bool32 ShouldSwitchIfAllMovesBad(u32 battler)
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
{
aiMove = gBattleMons[battler].moves[moveIndex];
if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) > UQ_4_12(0.0) && aiMove != MOVE_NONE)
if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) > UQ_4_12(0.0) && aiMove != MOVE_NONE
&& (!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;
}
}
if (RandomPercentage(RNG_AI_SWITCH_ALL_MOVES_BAD, GetSwitchChance(SHOULD_SWITCH_ALL_MOVES_BAD)))
return SetSwitchinAndSwitch(battler, PARTY_SIZE);
{
if (AI_DATA->mostSuitableMonId[battler] == PARTY_SIZE) // No good candidate mons, find any one that can deal damage
return FindMonWithMoveOfEffectiveness(battler, opposingBattler, UQ_4_12(1.0));
else // Good candidate mon, send that in
return SetSwitchinAndSwitch(battler, PARTY_SIZE);
}
return FALSE;
}
static bool32 FindMonThatHitsWonderGuard(u32 battler)
{
u32 opposingBattler = GetOppositeBattler(battler);
s32 i, j;
s32 firstId;
s32 lastId; // + 1
struct Pokemon *party = NULL;
u16 move;
u32 i, move;
if (IsDoubleBattle())
return FALSE;
@ -391,33 +426,10 @@ static bool32 FindMonThatHitsWonderGuard(u32 battler)
}
}
// Get party information.
GetAIPartyIndexes(battler, &firstId, &lastId);
party = GetBattlerParty(battler);
if (RandomPercentage(RNG_AI_SWITCH_WONDER_GUARD, GetSwitchChance(SHOULD_SWITCH_WONDER_GUARD)))
return FindMonWithMoveOfEffectiveness(battler, opposingBattler, UQ_4_12(2.0));
// Find a Pokémon in the party that has a super effective move.
for (i = firstId; i < lastId; i++)
{
if (!IsValidForBattle(&party[i]))
continue;
if (i == gBattlerPartyIndexes[battler])
continue;
if (IsAceMon(battler, i))
continue;
for (j = 0; j < MAX_MON_MOVES; j++)
{
move = GetMonData(&party[i], MON_DATA_MOVE1 + j);
if (move != MOVE_NONE)
{
// Found a mon
if (AI_GetMoveEffectiveness(move, battler, opposingBattler) >= UQ_4_12(2.0) && RandomPercentage(RNG_AI_SWITCH_WONDER_GUARD, GetSwitchChance(SHOULD_SWITCH_WONDER_GUARD)))
return SetSwitchinAndSwitch(battler, i);
}
}
}
return FALSE; // There is not a single Pokémon in the party that has a super effective move against a mon with Wonder Guard.
return FALSE;
}
static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler)

View File

@ -82,6 +82,22 @@ AI_SINGLE_BATTLE_TEST("AI will switch out if it has no move that affects the pla
}
}
AI_SINGLE_BATTLE_TEST("When AI switches out due to having no move that affects the player, AI will send in a mon that can hit the player, even if not ideal")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES);
PLAYER(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL, MOVE_CELEBRATE); }
OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ABRA) { Level(5); Moves(MOVE_CONFUSION); }
OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); }
} WHEN {
TURN { MOVE(player, MOVE_SHADOW_BALL); EXPECT_SWITCH(opponent, 2); EXPECT_SEND_OUT(opponent, 4); }
TURN { MOVE(player, MOVE_SHADOW_BALL); EXPECT_MOVE(opponent, MOVE_TACKLE); }
}
}
AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spots in a double battle (Wonder Guard)")
{
PASSES_RANDOMLY(SHOULD_SWITCH_WONDER_GUARD_PERCENTAGE, 100, RNG_AI_SWITCH_WONDER_GUARD);