Adjust switch AI based on move scoring (#6615)
This commit is contained in:
parent
3f5335c4ba
commit
c8fa4442d9
@ -32,6 +32,7 @@ enum ShouldSwitchScenario
|
||||
SHOULD_SWITCH_CHOICE_LOCKED,
|
||||
SHOULD_SWITCH_ATTACKING_STAT_MINUS_TWO,
|
||||
SHOULD_SWITCH_ATTACKING_STAT_MINUS_THREE_PLUS,
|
||||
SHOULD_SWITCH_ALL_SCORES_BAD,
|
||||
};
|
||||
|
||||
enum SwitchType
|
||||
@ -45,5 +46,6 @@ void AI_TrySwitchOrUseItem(u32 battler);
|
||||
u32 GetMostSuitableMonToSwitchInto(u32 battler, enum SwitchType switchType);
|
||||
bool32 ShouldSwitch(u32 battler);
|
||||
bool32 IsMonGrounded(u16 heldItemEffect, u32 ability, u8 type1, u8 type2);
|
||||
void ModifySwitchAfterMoveScoring(u32 battler);
|
||||
|
||||
#endif // GUARD_BATTLE_AI_SWITCH_ITEMS_H
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
#define SHOULD_SWITCH_CHOICE_LOCKED_PERCENTAGE 100 // Only if locked into status move
|
||||
#define SHOULD_SWITCH_ATTACKING_STAT_MINUS_TWO_PERCENTAGE 50
|
||||
#define SHOULD_SWITCH_ATTACKING_STAT_MINUS_THREE_PLUS_PERCENTAGE 100
|
||||
#define SHOULD_SWITCH_ALL_SCORES_BAD_PERCENTAGE 100
|
||||
|
||||
// AI smart switching chances for bad statuses
|
||||
#define SHOULD_SWITCH_PERISH_SONG_PERCENTAGE 100
|
||||
@ -46,6 +47,8 @@
|
||||
|
||||
// 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.
|
||||
#define AI_BAD_SCORE_THRESHOLD 90 // Move scores beneath this threshold are considered "bad" when deciding switching
|
||||
#define AI_GOOD_SCORE_THRESHOLD 100 // Move scores above this threshold are considered "good" when deciding switching
|
||||
|
||||
// AI held item-based move scoring
|
||||
#define LOW_ACCURACY_THRESHOLD 75 // Moves with accuracy equal OR below this value are considered low accuracy
|
||||
|
||||
@ -190,6 +190,7 @@ enum RandomTag
|
||||
RNG_AI_SWITCH_TRAPPER,
|
||||
RNG_AI_SWITCH_FREE_TURN,
|
||||
RNG_AI_SWITCH_ALL_MOVES_BAD,
|
||||
RNG_AI_SWITCH_ALL_SCORES_BAD,
|
||||
RNG_AI_PP_STALL_DISREGARD_MOVE,
|
||||
RNG_SHELL_SIDE_ARM,
|
||||
RNG_RANDOM_TARGET,
|
||||
|
||||
@ -283,11 +283,11 @@ bool32 BattlerChoseNonMoveAction(void)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void SetupAISwitchingData(u32 battler, enum SwitchType switchType)
|
||||
void SetupAIPredictionData(u32 battler, enum SwitchType switchType)
|
||||
{
|
||||
s32 opposingBattler = GetOppositeBattler(battler);
|
||||
|
||||
// AI's predicting data
|
||||
// Switch prediction
|
||||
if ((AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_SWITCH))
|
||||
{
|
||||
AI_DATA->aiSwitchPredictionInProgress = TRUE;
|
||||
@ -302,11 +302,8 @@ void SetupAISwitchingData(u32 battler, enum SwitchType switchType)
|
||||
AI_DATA->predictingSwitch = RandomPercentage(RNG_AI_PREDICT_SWITCH, PREDICT_SWITCH_CHANCE);
|
||||
}
|
||||
|
||||
// AI's data
|
||||
AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, switchType);
|
||||
if (ShouldSwitch(battler))
|
||||
AI_DATA->shouldSwitch |= (1u << battler);
|
||||
gBattleStruct->prevTurnSpecies[battler] = gBattleMons[battler].species;
|
||||
// TODO Move prediction
|
||||
// ModifySwitchAfterMoveScoring(opposingBattler);
|
||||
}
|
||||
|
||||
void ComputeBattlerDecisions(u32 battler)
|
||||
@ -323,9 +320,21 @@ void ComputeBattlerDecisions(u32 battler)
|
||||
enum SwitchType switchType = (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_RISKY) ? SWITCH_AFTER_KO : SWITCH_MID_BATTLE;
|
||||
|
||||
AI_DATA->aiCalcInProgress = TRUE;
|
||||
|
||||
// Setup battler and prediction data
|
||||
BattleAI_SetupAIData(0xF, battler);
|
||||
SetupAISwitchingData(battler, switchType);
|
||||
SetupAIPredictionData(battler, switchType);
|
||||
|
||||
// AI's own switching data
|
||||
AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, switchType);
|
||||
if (ShouldSwitch(battler))
|
||||
AI_DATA->shouldSwitch |= (1u << battler);
|
||||
gBattleStruct->prevTurnSpecies[battler] = gBattleMons[battler].species;
|
||||
|
||||
// AI's move scoring
|
||||
gAiBattleData->chosenMoveIndex[battler] = BattleAI_ChooseMoveIndex(battler); // Calculate score and chose move index
|
||||
ModifySwitchAfterMoveScoring(battler);
|
||||
|
||||
AI_DATA->aiCalcInProgress = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,6 +101,8 @@ u32 GetSwitchChance(enum ShouldSwitchScenario shouldSwitchScenario)
|
||||
return SHOULD_SWITCH_ATTACKING_STAT_MINUS_TWO_PERCENTAGE;
|
||||
case SHOULD_SWITCH_ATTACKING_STAT_MINUS_THREE_PLUS:
|
||||
return SHOULD_SWITCH_ATTACKING_STAT_MINUS_THREE_PLUS_PERCENTAGE;
|
||||
case SHOULD_SWITCH_ALL_SCORES_BAD:
|
||||
return SHOULD_SWITCH_ALL_SCORES_BAD_PERCENTAGE;
|
||||
default:
|
||||
return 100;
|
||||
}
|
||||
@ -1063,24 +1065,6 @@ static bool32 ShouldSwitchIfAttackingStatsLowered(u32 battler)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static bool32 HasGoodSubstituteMove(u32 battler)
|
||||
{
|
||||
int i;
|
||||
u32 aiMove, opposingBattler = GetOppositeBattler(battler);
|
||||
enum BattleMoveEffects aiMoveEffect;
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
aiMove = gBattleMons[battler].moves[i];
|
||||
aiMoveEffect = GetMoveEffect(aiMove);
|
||||
if (IsSubstituteEffect(aiMoveEffect))
|
||||
{
|
||||
if (IncreaseSubstituteMoveScore(battler, opposingBattler, aiMove) > 0)
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool32 ShouldSwitch(u32 battler)
|
||||
{
|
||||
u32 battlerIn1, battlerIn2;
|
||||
@ -1154,8 +1138,6 @@ bool32 ShouldSwitch(u32 battler)
|
||||
return TRUE;
|
||||
if ((AI_THINKING_STRUCT->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING) && (CanMonSurviveHazardSwitchin(battler) == FALSE))
|
||||
return FALSE;
|
||||
if (HasGoodSubstituteMove(battler))
|
||||
return FALSE;
|
||||
if (ShouldSwitchIfTrapperInParty(battler))
|
||||
return TRUE;
|
||||
if (FindMonThatAbsorbsOpponentsMove(battler))
|
||||
@ -1198,6 +1180,109 @@ bool32 ShouldSwitch(u32 battler)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool32 ShouldSwitchIfAllScoresBad(u32 battler)
|
||||
{
|
||||
u32 i, score, opposingBattler = GetOppositeBattler(battler);
|
||||
if (!(AI_THINKING_STRUCT->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING))
|
||||
return FALSE;
|
||||
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
score = gAiBattleData->finalScore[battler][opposingBattler][i];
|
||||
if (score > AI_BAD_SCORE_THRESHOLD)
|
||||
return FALSE;
|
||||
}
|
||||
if (RandomPercentage(RNG_AI_SWITCH_ALL_SCORES_BAD, GetSwitchChance(SHOULD_SWITCH_ALL_SCORES_BAD)))
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool32 ShouldStayInToUseMove(u32 battler)
|
||||
{
|
||||
u32 i, aiMove, opposingBattler = GetOppositeBattler(battler);
|
||||
enum BattleMoveEffects aiMoveEffect;
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
aiMove = gBattleMons[battler].moves[i];
|
||||
aiMoveEffect = GetMoveEffect(aiMove);
|
||||
if (aiMoveEffect == EFFECT_REVIVAL_BLESSING || IsSwitchOutEffect(aiMoveEffect))
|
||||
{
|
||||
if (gAiBattleData->finalScore[battler][opposingBattler][i] > AI_GOOD_SCORE_THRESHOLD)
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void ModifySwitchAfterMoveScoring(u32 battler)
|
||||
{
|
||||
u32 battlerIn1, battlerIn2;
|
||||
s32 firstId;
|
||||
s32 lastId; // + 1
|
||||
struct Pokemon *party;
|
||||
s32 i;
|
||||
s32 availableToSwitch;
|
||||
|
||||
if (gBattleMons[battler].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION))
|
||||
return;
|
||||
if (gStatuses3[battler] & STATUS3_ROOTED)
|
||||
return;
|
||||
if (IsAbilityPreventingEscape(battler))
|
||||
return;
|
||||
if (gBattleTypeFlags & BATTLE_TYPE_ARENA)
|
||||
return;
|
||||
|
||||
// Sequence Switching AI never switches mid-battle
|
||||
if (AI_THINKING_STRUCT->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SEQUENCE_SWITCHING)
|
||||
return;
|
||||
|
||||
availableToSwitch = 0;
|
||||
|
||||
if (IsDoubleBattle())
|
||||
{
|
||||
u32 partner = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerAtPosition(battler)));
|
||||
battlerIn1 = battler;
|
||||
if (gAbsentBattlerFlags & (1u << partner))
|
||||
battlerIn2 = battler;
|
||||
else
|
||||
battlerIn2 = partner;
|
||||
}
|
||||
else
|
||||
{
|
||||
battlerIn1 = battler;
|
||||
battlerIn2 = battler;
|
||||
}
|
||||
|
||||
GetAIPartyIndexes(battler, &firstId, &lastId);
|
||||
party = GetBattlerParty(battler);
|
||||
|
||||
for (i = firstId; i < lastId; i++)
|
||||
{
|
||||
if (!IsValidForBattle(&party[i]))
|
||||
continue;
|
||||
if (i == gBattlerPartyIndexes[battlerIn1])
|
||||
continue;
|
||||
if (i == gBattlerPartyIndexes[battlerIn2])
|
||||
continue;
|
||||
if (i == gBattleStruct->monToSwitchIntoId[battlerIn1])
|
||||
continue;
|
||||
if (i == gBattleStruct->monToSwitchIntoId[battlerIn2])
|
||||
continue;
|
||||
if (IsAceMon(battler, i))
|
||||
continue;
|
||||
|
||||
availableToSwitch++;
|
||||
}
|
||||
|
||||
if (availableToSwitch == 0)
|
||||
return;
|
||||
|
||||
if (ShouldSwitchIfAllScoresBad(battler))
|
||||
AI_DATA->shouldSwitch |= (1u << battler);
|
||||
else if (ShouldStayInToUseMove(battler))
|
||||
AI_DATA->shouldSwitch &= ~(1u << battler);
|
||||
}
|
||||
|
||||
bool32 IsSwitchinValid(u32 battler)
|
||||
{
|
||||
// Edge case: See if partner already chose to switch into the same mon
|
||||
|
||||
@ -1132,3 +1132,24 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will consider choice-locked
|
||||
TURN { MOVE(player, MOVE_EARTHQUAKE); EXPECT_MOVE(opponent, MOVE_TACKLE); item == ITEM_NONE ? EXPECT_SEND_OUT(opponent, 1) : EXPECT_SEND_OUT(opponent, 2); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if all moves deal zero damage")
|
||||
{
|
||||
PASSES_RANDOMLY(SHOULD_SWITCH_ALL_SCORES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_SCORES_BAD);
|
||||
GIVEN {
|
||||
ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_WILL_O_WISP);
|
||||
ASSUME(GetMoveEffect(MOVE_POLTERGEIST) == EFFECT_POLTERGEIST);
|
||||
ASSUME(GetMoveType(MOVE_SCALD) == TYPE_WATER);
|
||||
ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND);
|
||||
ASSUME(gSpeciesInfo[SPECIES_MANTINE].types[1] == TYPE_FLYING);
|
||||
ASSUME(ItemId_GetHoldEffect(ITEM_WATER_GEM) == HOLD_EFFECT_GEMS);
|
||||
ASSUME(ItemId_GetSecondaryId(ITEM_WATER_GEM) == TYPE_WATER);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT);
|
||||
PLAYER(SPECIES_MANTINE) { Speed(5); Moves(MOVE_ROOST, MOVE_SCALD); Ability(ABILITY_WATER_VEIL); Item(ITEM_WATER_GEM); }
|
||||
OPPONENT(SPECIES_DUSKNOIR) { Speed(6); Moves(MOVE_WILL_O_WISP, MOVE_POLTERGEIST, MOVE_EARTHQUAKE); }
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Speed(6); Moves(MOVE_TACKLE); }
|
||||
} WHEN {
|
||||
TURN { EXPECT_MOVE(opponent, MOVE_POLTERGEIST); MOVE(player, MOVE_SCALD); }
|
||||
TURN { EXPECT_SWITCH(opponent, 1); MOVE(player, MOVE_ROOST); }
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user