Refactor ShouldSwitchIfAllBadMoves (#5452)
* Refactor ShouldSwitchIfAllBadMoves
* Rest of bugfix
* Enable Shed Tail test
* Revert "Enable Shed Tail test"
This reverts commit e0b9a3affc
.
* Better names for i, j
* Fix MAX_MON_MOVES that should be MAX_BATTLERS_COUNT
* battlerIndex / moveIndex fix
* Update src/battle_controller_player_partner.c
---------
Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
This commit is contained in:
parent
d4387d880c
commit
9882e0aea6
7 changed files with 220 additions and 179 deletions
|
@ -369,8 +369,7 @@ struct AiLogicData
|
|||
u8 effectiveness[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
|
||||
u8 moveAccuracy[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
|
||||
u8 moveLimitations[MAX_BATTLERS_COUNT];
|
||||
u8 shouldSwitchIfBadMoves; // Because all available moves have no/little effect. Each bit per battler.
|
||||
u8 monToSwitchId[MAX_BATTLERS_COUNT]; // ID of the mon to switch.
|
||||
u8 monToSwitchInId[MAX_BATTLERS_COUNT]; // ID of the mon to switch in.
|
||||
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
|
||||
u8 weatherHasEffect:1; // The same as WEATHER_HAS_EFFECT. Stored here, so it's called only once.
|
||||
|
|
|
@ -10,7 +10,6 @@ typedef s32 (*AiScoreFunc)(u32, u32, u32, s32);
|
|||
// 0 - 3 are move idx
|
||||
#define AI_CHOICE_FLEE 4
|
||||
#define AI_CHOICE_WATCH 5
|
||||
#define AI_CHOICE_SWITCH 7
|
||||
|
||||
// for AI_WhoStrikesFirst
|
||||
#define AI_IS_FASTER 1
|
||||
|
|
|
@ -496,89 +496,6 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData)
|
|||
AI_DATA->aiCalcInProgress = FALSE;
|
||||
}
|
||||
|
||||
static bool32 AI_SwitchMonIfSuitable(u32 battler, bool32 doubleBattle)
|
||||
{
|
||||
u32 monToSwitchId = AI_DATA->mostSuitableMonId[battler];
|
||||
if (monToSwitchId != PARTY_SIZE && IsValidForBattle(&GetBattlerParty(battler)[monToSwitchId]))
|
||||
{
|
||||
gBattleMoveDamage = monToSwitchId;
|
||||
// Edge case: See if partner already chose to switch into the same mon
|
||||
if (doubleBattle)
|
||||
{
|
||||
u32 partner = BATTLE_PARTNER(battler);
|
||||
if (AI_DATA->shouldSwitchIfBadMoves & (1u << partner) && AI_DATA->monToSwitchId[partner] == monToSwitchId)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
AI_DATA->shouldSwitchIfBadMoves |= 1 << battler;
|
||||
AI_DATA->monToSwitchId[battler] = monToSwitchId;
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle)
|
||||
{
|
||||
u32 i, j;
|
||||
// If can switch.
|
||||
if (CountUsablePartyMons(battler) > 0
|
||||
&& !IsBattlerTrapped(battler, TRUE)
|
||||
&& !(gBattleTypeFlags & (BATTLE_TYPE_ARENA | BATTLE_TYPE_PALACE))
|
||||
&& AI_THINKING_STRUCT->aiFlags[battler] & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS)
|
||||
&& !(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING))
|
||||
{
|
||||
// Consider switching if all moves are worthless to use.
|
||||
if (GetTotalBaseStat(gBattleMons[battler].species) >= 310 // Mon is not weak.
|
||||
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2) // Mon has more than 50% of its HP
|
||||
{
|
||||
s32 cap = AI_THINKING_STRUCT->aiFlags[battler] & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93;
|
||||
if (doubleBattle)
|
||||
{
|
||||
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
||||
{
|
||||
if (i != battler && IsBattlerAlive(i))
|
||||
{
|
||||
for (j = 0; j < MAX_MON_MOVES; j++)
|
||||
{
|
||||
if (gBattleStruct->aiFinalScore[battler][i][j] > cap)
|
||||
break;
|
||||
}
|
||||
if (j != MAX_MON_MOVES)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == MAX_BATTLERS_COUNT && AI_SwitchMonIfSuitable(battler, doubleBattle))
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (AI_THINKING_STRUCT->score[i] > cap)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == MAX_MON_MOVES && AI_SwitchMonIfSuitable(battler, doubleBattle))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Consider switching if your mon with truant is bodied by Protect spam.
|
||||
// Or is using a double turn semi invulnerable move(such as Fly) and is faster.
|
||||
if (AI_DATA->abilities[battler] == ABILITY_TRUANT
|
||||
&& IsTruantMonVulnerable(battler, gBattlerTarget)
|
||||
&& gDisableStructs[battler].truantCounter
|
||||
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2
|
||||
&& AI_SwitchMonIfSuitable(battler, doubleBattle))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static u32 ChooseMoveOrAction_Singles(u32 battlerAi)
|
||||
{
|
||||
u8 currentMoveArray[MAX_MON_MOVES];
|
||||
|
@ -609,10 +526,6 @@ static u32 ChooseMoveOrAction_Singles(u32 battlerAi)
|
|||
if (AI_THINKING_STRUCT->aiAction & AI_ACTION_WATCH)
|
||||
return AI_CHOICE_WATCH;
|
||||
|
||||
// Switch mon if there are no good moves to use.
|
||||
if (AI_ShouldSwitchIfBadMoves(battlerAi, FALSE))
|
||||
return AI_CHOICE_SWITCH;
|
||||
|
||||
numOfBestMoves = 1;
|
||||
currentMoveArray[0] = AI_THINKING_STRUCT->score[0];
|
||||
consideredMoveArray[0] = 0;
|
||||
|
@ -733,10 +646,6 @@ static u32 ChooseMoveOrAction_Doubles(u32 battlerAi)
|
|||
}
|
||||
}
|
||||
|
||||
// Switch mon if all of the moves are bad to use against any of the target.
|
||||
if (AI_ShouldSwitchIfBadMoves(battlerAi, TRUE))
|
||||
return AI_CHOICE_SWITCH;
|
||||
|
||||
mostMovePoints = bestMovePointsForTarget[0];
|
||||
mostViableTargetsArray[0] = 0;
|
||||
mostViableTargetsNo = 1;
|
||||
|
|
|
@ -65,6 +65,26 @@ void GetAIPartyIndexes(u32 battler, s32 *firstId, s32 *lastId)
|
|||
}
|
||||
}
|
||||
|
||||
bool32 IsSwitchinIdValid(u32 battler, u32 switchinId)
|
||||
{
|
||||
if (IsDoubleBattle())
|
||||
{
|
||||
u32 partner = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerAtPosition(battler)));
|
||||
// Edge case: See if partner already chose to switch into the same mon
|
||||
if (switchinId == PARTY_SIZE) // Generic switch
|
||||
{
|
||||
if ((AI_DATA->shouldSwitch & (1u << partner)) && AI_DATA->monToSwitchInId[partner] == AI_DATA->mostSuitableMonId[battler])
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((AI_DATA->shouldSwitch & (1u << partner)) && AI_DATA->monToSwitchInId[partner] == switchinId)
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -185,9 +205,12 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult)
|
|||
|
||||
// Switch mon out
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// General bad type matchups have more wiggle room
|
||||
|
@ -208,6 +231,75 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult)
|
|||
|
||||
// Switch mon out
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static bool32 ShouldSwitchIfAllMovesBad(u32 battler, bool32 emitResult)
|
||||
{
|
||||
u32 battlerIndex, moveIndex;
|
||||
bool32 switchOut = FALSE;
|
||||
|
||||
// Consider switching if all moves are worthless to use.
|
||||
if (GetTotalBaseStat(gBattleMons[battler].species) >= 310 // Mon is not weak.
|
||||
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2) // Mon has more than 50% of its HP
|
||||
{
|
||||
s32 cap = AI_THINKING_STRUCT->aiFlags[battler] & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93;
|
||||
if (IsDoubleBattle())
|
||||
{
|
||||
for (battlerIndex = 0; battlerIndex < MAX_BATTLERS_COUNT; battlerIndex++)
|
||||
{
|
||||
if (battlerIndex != battler && IsBattlerAlive(battlerIndex))
|
||||
{
|
||||
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
|
||||
{
|
||||
if (gBattleStruct->aiFinalScore[battler][battlerIndex][moveIndex] > cap)
|
||||
break;
|
||||
}
|
||||
if (moveIndex != MAX_MON_MOVES)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (battlerIndex == MAX_BATTLERS_COUNT && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE)
|
||||
switchOut = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
|
||||
{
|
||||
if (AI_THINKING_STRUCT->score[moveIndex] > cap)
|
||||
break;
|
||||
}
|
||||
|
||||
if (moveIndex == MAX_MON_MOVES && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE)
|
||||
switchOut = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Consider switching if your mon with truant is bodied by Protect spam.
|
||||
// Or is using a double turn semi invulnerable move(such as Fly) and is faster.
|
||||
if (AI_DATA->abilities[battler] == ABILITY_TRUANT
|
||||
&& IsTruantMonVulnerable(battler, gBattlerTarget)
|
||||
&& gDisableStructs[battler].truantCounter
|
||||
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2
|
||||
&& AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE)
|
||||
{
|
||||
switchOut = TRUE;
|
||||
}
|
||||
|
||||
if (switchOut)
|
||||
{
|
||||
// Switch mon out
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
|
@ -216,22 +308,6 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult)
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
static bool32 ShouldSwitchIfAllBadMoves(u32 battler, bool32 emitResult)
|
||||
{
|
||||
if (AI_DATA->shouldSwitchIfBadMoves & (1u << battler))
|
||||
{
|
||||
AI_DATA->shouldSwitchIfBadMoves &= ~(1u << battler);
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = AI_DATA->monToSwitchId[battler];
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static bool32 ShouldSwitchIfWonderGuard(u32 battler, bool32 emitResult)
|
||||
{
|
||||
u8 opposingPosition;
|
||||
|
@ -288,9 +364,12 @@ static bool32 ShouldSwitchIfWonderGuard(u32 battler, bool32 emitResult)
|
|||
{
|
||||
// We found a mon.
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = i;
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -403,9 +482,12 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler, bool32 emitResult)
|
|||
{
|
||||
// we found a mon.
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = i;
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -445,9 +527,12 @@ static bool32 FindMonThatTrapsOpponent(u32 battler, bool32 emitResult)
|
|||
if (i == AI_DATA->mostSuitableMonId[battler]) // If mon in slot i is the most suitable switchin candidate, then it's a trapper than wins 1v1
|
||||
{
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = i;
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -520,9 +605,12 @@ static bool32 ShouldSwitchIfGameStatePrompt(u32 battler, bool32 emitResult)
|
|||
|| GetMonAbility(&party[i]) == ABILITY_ELECTRIC_SURGE)) //Ally has Misty or Electric Surge
|
||||
{
|
||||
*(gBattleStruct->AI_monToSwitchIntoId + BATTLE_PARTNER(battler)) = i;
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
|
||||
switchMon = FALSE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
|
||||
switchMon = FALSE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -611,9 +699,13 @@ static bool32 ShouldSwitchIfGameStatePrompt(u32 battler, bool32 emitResult)
|
|||
{
|
||||
if (!monIdChosen)
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -668,10 +760,13 @@ static bool32 ShouldSwitchIfAbilityBenefit(u32 battler, bool32 emitResult)
|
|||
}
|
||||
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
|
||||
|
||||
return TRUE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static bool32 HasSuperEffectiveMoveAgainstOpponents(u32 battler, bool32 noRng)
|
||||
|
@ -811,9 +906,12 @@ static bool32 FindMonWithFlagsAndSuperEffective(u32 battler, u16 flags, u32 modu
|
|||
if (AI_GetTypeEffectiveness(move, battler, battlerIn1) >= UQ_4_12(2.0) && Random() % moduloPercent == 0)
|
||||
{
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = i;
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -902,9 +1000,12 @@ static bool32 ShouldSwitchIfEncored(u32 battler, bool32 emitResult)
|
|||
if (Random() & 1)
|
||||
{
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
|
@ -919,9 +1020,13 @@ static bool32 ShouldSwitchIfBadChoiceLock(u32 battler, bool32 emitResult)
|
|||
if (gMovesInfo[gLastUsedMove].category == DAMAGE_CATEGORY_STATUS)
|
||||
{
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -950,18 +1055,24 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult)
|
|||
if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1))
|
||||
{
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If at -3 or worse, switch out regardless
|
||||
else if (attackingStage < DEFAULT_STAT_STAGE - 2)
|
||||
{
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -977,16 +1088,24 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult)
|
|||
if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1))
|
||||
{
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If at -3 or worse, switch out regardless
|
||||
else if (spAttackingStage < DEFAULT_STAT_STAGE - 2)
|
||||
{
|
||||
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
|
||||
{
|
||||
if (emitResult)
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
|
@ -1019,11 +1138,12 @@ bool32 ShouldSwitch(u32 battler, bool32 emitResult)
|
|||
|
||||
if (IsDoubleBattle())
|
||||
{
|
||||
u32 partner = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerAtPosition(battler)));
|
||||
battlerIn1 = battler;
|
||||
if (gAbsentBattlerFlags & (1u << GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)))))
|
||||
if (gAbsentBattlerFlags & (1u << partner))
|
||||
battlerIn2 = battler;
|
||||
else
|
||||
battlerIn2 = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)));
|
||||
battlerIn2 = partner;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1083,7 +1203,7 @@ bool32 ShouldSwitch(u32 battler, bool32 emitResult)
|
|||
//These Functions can prompt switch to generic pary members
|
||||
if ((AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_SWITCHING) && (CanMonSurviveHazardSwitchin(battler) == FALSE))
|
||||
return FALSE;
|
||||
if (ShouldSwitchIfAllBadMoves(battler, emitResult))
|
||||
if (ShouldSwitchIfAllMovesBad(battler, emitResult))
|
||||
return TRUE;
|
||||
if (ShouldSwitchIfAbilityBenefit(battler, emitResult))
|
||||
return TRUE;
|
||||
|
@ -1170,6 +1290,7 @@ void AI_TrySwitchOrUseItem(u32 battler)
|
|||
}
|
||||
|
||||
*(gBattleStruct->monToSwitchIntoId + battler) = gBattleStruct->AI_monToSwitchIntoId[battler];
|
||||
AI_DATA->monToSwitchInId[battler] = gBattleStruct->AI_monToSwitchIntoId[battler];
|
||||
return;
|
||||
}
|
||||
else if (ShouldUseItem(battler))
|
||||
|
|
|
@ -550,9 +550,6 @@ static void OpponentHandleChooseMove(u32 battler)
|
|||
case AI_CHOICE_FLEE:
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_RUN, 0);
|
||||
break;
|
||||
case AI_CHOICE_SWITCH:
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, 0xFFFF);
|
||||
break;
|
||||
case 6:
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 15, gBattlerTarget);
|
||||
break;
|
||||
|
|
|
@ -354,31 +354,24 @@ static void PlayerPartnerHandleChooseMove(u32 battler)
|
|||
chosenMoveId = gBattleStruct->aiMoveOrAction[battler];
|
||||
gBattlerTarget = gBattleStruct->aiChosenTarget[battler];
|
||||
|
||||
if (chosenMoveId == AI_CHOICE_SWITCH)
|
||||
if (gMovesInfo[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED))
|
||||
gBattlerTarget = battler;
|
||||
else if (gMovesInfo[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH)
|
||||
{
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, 0xFFFF);
|
||||
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
|
||||
if (gAbsentBattlerFlags & (1u << gBattlerTarget))
|
||||
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
|
||||
}
|
||||
// If partner can and should use a gimmick (considering trainer data), do it
|
||||
if (gBattleStruct->gimmick.usableGimmick[battler] != GIMMICK_NONE
|
||||
&& !(gBattleStruct->gimmick.usableGimmick[battler] == GIMMICK_Z_MOVE
|
||||
&& !ShouldUseZMove(battler, gBattlerTarget, moveInfo->moves[chosenMoveId])))
|
||||
{
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_GIMMICK) | (gBattlerTarget << 8));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (gMovesInfo[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED))
|
||||
gBattlerTarget = battler;
|
||||
if (gMovesInfo[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH)
|
||||
{
|
||||
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
|
||||
if (gAbsentBattlerFlags & (1u << gBattlerTarget))
|
||||
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
|
||||
}
|
||||
// If partner can and should use a gimmick (considering trainer data), do it
|
||||
if (gBattleStruct->gimmick.usableGimmick[battler] != GIMMICK_NONE
|
||||
&& !(gBattleStruct->gimmick.usableGimmick[battler] == GIMMICK_Z_MOVE
|
||||
&& !ShouldUseZMove(battler, gBattlerTarget, moveInfo->moves[chosenMoveId])))
|
||||
{
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_GIMMICK) | (gBattlerTarget << 8));
|
||||
}
|
||||
else
|
||||
{
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8));
|
||||
}
|
||||
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8));
|
||||
}
|
||||
|
||||
PlayerPartnerBufferExecCompleted(battler);
|
||||
|
|
|
@ -39,7 +39,7 @@ AI_SINGLE_BATTLE_TEST("AI switches if Perish Song is about to kill")
|
|||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spots in a double battle")
|
||||
AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spots in a double battle (all bad moves)")
|
||||
{
|
||||
u32 flags;
|
||||
|
||||
|
@ -67,6 +67,29 @@ AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spot
|
|||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spots in a double battle (Wonder Guard)")
|
||||
{
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING);
|
||||
PLAYER(SPECIES_SHEDINJA);
|
||||
PLAYER(SPECIES_SHEDINJA);
|
||||
// No moves to damage player.
|
||||
OPPONENT(SPECIES_LINOONE) { Moves(MOVE_TACKLE); }
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
|
||||
OPPONENT(SPECIES_LINOONE) { Moves(MOVE_TACKLE); }
|
||||
OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); }
|
||||
} WHEN {
|
||||
TURN { EXPECT_SWITCH(opponentLeft, 3); };
|
||||
} SCENE {
|
||||
MESSAGE("{PKMN} TRAINER LEAF withdrew Linoone!");
|
||||
MESSAGE("{PKMN} TRAINER LEAF sent out Gengar!");
|
||||
NONE_OF {
|
||||
MESSAGE("{PKMN} TRAINER LEAF withdrew Zigzagoon!");
|
||||
MESSAGE("{PKMN} TRAINER LEAF sent out Gengar!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: U-Turn will send out Ace Mon if it's the only one remaining")
|
||||
{
|
||||
GIVEN {
|
||||
|
|
Loading…
Reference in a new issue