Extend AI_FLAG_SMART_SWITCHING for Encore / hazards / lowered attacking stats (#3557)
* Consider Encore / lowered stats / hazards * Fix poorly thought out Return FALSE * Incorporate Egg's feedback * Tests for hazards and stats, and some fixes --------- Co-authored-by: Bassoonian <iasperbassoonian@gmail.com>
This commit is contained in:
parent
2b1d36db46
commit
131bb94d73
2 changed files with 194 additions and 1 deletions
|
@ -28,6 +28,7 @@ static bool8 ShouldUseItem(u32 battler);
|
|||
static bool32 AiExpectsToFaintPlayer(u32 battler);
|
||||
static bool32 AI_ShouldHeal(u32 battler, u32 healAmount);
|
||||
static bool32 AI_OpponentCanFaintAiWithMod(u32 battler, u32 healAmount);
|
||||
static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon);
|
||||
|
||||
static void InitializeSwitchinCandidate(struct Pokemon *mon)
|
||||
{
|
||||
|
@ -774,6 +775,153 @@ static bool8 FindMonWithFlagsAndSuperEffective(u32 battler, u16 flags, u8 modulo
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
static bool32 CanMonSurviveHazardSwitchin(u32 battler)
|
||||
{
|
||||
u32 battlerIn1, battlerIn2;
|
||||
u32 hazardDamage = 0, battlerHp = gBattleMons[battler].hp;
|
||||
u32 ability = GetBattlerAbility(battler), aiMove;
|
||||
s32 firstId, lastId, i, j;
|
||||
struct Pokemon *party;
|
||||
|
||||
if (ability == ABILITY_REGENERATOR)
|
||||
battlerHp = (battlerHp * 133) / 100; // Account for Regenerator healing
|
||||
|
||||
hazardDamage = GetSwitchinHazardsDamage(battler, &gBattleMons[battler]);
|
||||
|
||||
// Battler will faint to hazards, check to see if another mon can clear them
|
||||
if (hazardDamage > battlerHp)
|
||||
{
|
||||
if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
|
||||
{
|
||||
battlerIn1 = battler;
|
||||
if (gAbsentBattlerFlags & gBitTable[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)))])
|
||||
battlerIn2 = battler;
|
||||
else
|
||||
battlerIn2 = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)));
|
||||
}
|
||||
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;
|
||||
|
||||
for (j = 0; j < MAX_MON_MOVES; j++)
|
||||
{
|
||||
aiMove = GetMonData(&party[i], MON_DATA_MOVE1 + j, NULL);
|
||||
if (aiMove == MOVE_RAPID_SPIN || aiMove == MOVE_DEFOG || aiMove == MOVE_MORTAL_SPIN || aiMove == MOVE_TIDY_UP)
|
||||
{
|
||||
// Have a mon that can clear the hazards, so switching out is okay
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Faints to hazards and party can't clear them, don't switch out
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static bool32 ShouldSwitchIfEncored(u32 battler)
|
||||
{
|
||||
// Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer
|
||||
if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING))
|
||||
return FALSE;
|
||||
|
||||
// If not Encored or if no good switchin, don't switch
|
||||
if (gDisableStructs[battler].encoredMove == MOVE_NONE || AI_DATA->mostSuitableMonId == PARTY_SIZE)
|
||||
return FALSE;
|
||||
|
||||
// Otherwise 50% chance to switch out
|
||||
if (Random() & 1)
|
||||
{
|
||||
*(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE;
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// AI should switch if it's become setup fodder and has something better to switch to
|
||||
static bool8 AreAttackingStatsLowered(u32 battler)
|
||||
{
|
||||
s8 attackingStage = gBattleMons[battler].statStages[STAT_ATK];
|
||||
s8 spAttackingStage = gBattleMons[battler].statStages[STAT_SPATK];
|
||||
|
||||
// Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer
|
||||
if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING))
|
||||
return FALSE;
|
||||
|
||||
// Physical attacker
|
||||
if (gBattleMons[battler].attack > gBattleMons[battler].spAttack)
|
||||
{
|
||||
// Don't switch if attack isn't below -1
|
||||
if (attackingStage > DEFAULT_STAT_STAGE - 2)
|
||||
return FALSE;
|
||||
// 50% chance if attack at -2 and have a good candidate mon
|
||||
else if (attackingStage == DEFAULT_STAT_STAGE - 2)
|
||||
{
|
||||
if (AI_DATA->mostSuitableMonId != PARTY_SIZE && (Random() & 1))
|
||||
{
|
||||
*(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE;
|
||||
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;
|
||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Special attacker
|
||||
else
|
||||
{
|
||||
// Don't switch if attack isn't below -1
|
||||
if (spAttackingStage > DEFAULT_STAT_STAGE - 2)
|
||||
return FALSE;
|
||||
// 50% chance if attack at -2 and have a good candidate mon
|
||||
else if (spAttackingStage == DEFAULT_STAT_STAGE - 2)
|
||||
{
|
||||
if (AI_DATA->mostSuitableMonId != PARTY_SIZE && (Random() & 1))
|
||||
{
|
||||
*(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE;
|
||||
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;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool32 ShouldSwitch(u32 battler)
|
||||
{
|
||||
u8 battlerIn1, battlerIn2;
|
||||
|
@ -857,12 +1005,18 @@ bool32 ShouldSwitch(u32 battler)
|
|||
return TRUE;
|
||||
|
||||
//These Functions can prompt switch to generic pary members
|
||||
if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING) && (CanMonSurviveHazardSwitchin(battler) == FALSE))
|
||||
return FALSE;
|
||||
if (ShouldSwitchIfAllBadMoves(battler))
|
||||
return TRUE;
|
||||
if (ShouldSwitchIfAbilityBenefit(battler))
|
||||
return TRUE;
|
||||
if (HasBadOdds(battler))
|
||||
return TRUE;
|
||||
if (ShouldSwitchIfEncored(battler))
|
||||
return TRUE;
|
||||
if (AreAttackingStatsLowered(battler))
|
||||
return TRUE;
|
||||
|
||||
//Removing switch capabilites under specific conditions
|
||||
//These Functions prevent the "FindMonWithFlagsAndSuperEffective" from getting out of hand.
|
||||
|
@ -1108,7 +1262,7 @@ static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon
|
|||
{
|
||||
// Stealth Rock
|
||||
if ((hazardFlags & SIDE_STATUS_STEALTH_ROCK) && heldItemEffect != HOLD_EFFECT_HEAVY_DUTY_BOOTS)
|
||||
hazardDamage += GetStealthHazardDamageByTypesAndHP(gBattleMoves[MOVE_STEALTH_ROCK].type, defType1, defType2, battleMon->hp);
|
||||
hazardDamage += GetStealthHazardDamageByTypesAndHP(gBattleMoves[MOVE_STEALTH_ROCK].type, defType1, defType2, battleMon->maxHP);
|
||||
// Spikes
|
||||
if ((hazardFlags & SIDE_STATUS_SPIKES) && IsMonGrounded(heldItemEffect, ability, defType1, defType2))
|
||||
{
|
||||
|
|
|
@ -608,3 +608,42 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Post-KO switches prioritize of
|
|||
TURN { MOVE(player, MOVE_WING_ATTACK) ; EXPECT_SEND_OUT(opponent, 2); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI switches out after sufficient stat drops")
|
||||
{
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
|
||||
PLAYER(SPECIES_HITMONTOP) { Level(30); Moves(MOVE_CHARM, MOVE_TACKLE); Ability(ABILITY_INTIMIDATE); Speed(5); }
|
||||
OPPONENT(SPECIES_GRIMER) { Level(30); Moves(MOVE_TACKLE); Speed(4); }
|
||||
OPPONENT(SPECIES_PONYTA) { Level(30); Moves(MOVE_HEADBUTT); Speed(4); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_CHARM) ;}
|
||||
TURN { MOVE(player, MOVE_TACKLE) ; EXPECT_SWITCH(opponent, 1); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will not switch out if Pokemon would faint to hazards unless party member can clear them")
|
||||
{
|
||||
u32 move1;
|
||||
|
||||
PARAMETRIZE{move1 = MOVE_TACKLE; }
|
||||
PARAMETRIZE{move1 = MOVE_RAPID_SPIN; }
|
||||
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
|
||||
PLAYER(SPECIES_HITMONTOP) { Level(30); Moves(MOVE_CHARM, MOVE_TACKLE, MOVE_STEALTH_ROCK, MOVE_EARTHQUAKE); Ability(ABILITY_INTIMIDATE); Speed(5); }
|
||||
OPPONENT(SPECIES_GRIMER) { Level(30); Moves(MOVE_TACKLE); Item(ITEM_FOCUS_SASH); Speed(4); }
|
||||
OPPONENT(SPECIES_PONYTA) { Level(30); Moves(MOVE_HEADBUTT, move1); Speed(4); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_STEALTH_ROCK) ;}
|
||||
TURN { MOVE(player, MOVE_EARTHQUAKE) ;}
|
||||
TURN { MOVE(player, MOVE_CHARM) ;}
|
||||
TURN { // If the AI has a mon that can remove hazards, don't prevent them switching out
|
||||
MOVE(player, MOVE_CHARM);
|
||||
if (move1 == MOVE_RAPID_SPIN)
|
||||
EXPECT_SWITCH(opponent, 1);
|
||||
else if (move1 == MOVE_TACKLE)
|
||||
EXPECT_MOVE(opponent, MOVE_TACKLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue