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 AiExpectsToFaintPlayer(u32 battler);
|
||||||
static bool32 AI_ShouldHeal(u32 battler, u32 healAmount);
|
static bool32 AI_ShouldHeal(u32 battler, u32 healAmount);
|
||||||
static bool32 AI_OpponentCanFaintAiWithMod(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)
|
static void InitializeSwitchinCandidate(struct Pokemon *mon)
|
||||||
{
|
{
|
||||||
|
@ -774,6 +775,153 @@ static bool8 FindMonWithFlagsAndSuperEffective(u32 battler, u16 flags, u8 modulo
|
||||||
return FALSE;
|
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)
|
bool32 ShouldSwitch(u32 battler)
|
||||||
{
|
{
|
||||||
u8 battlerIn1, battlerIn2;
|
u8 battlerIn1, battlerIn2;
|
||||||
|
@ -857,12 +1005,18 @@ bool32 ShouldSwitch(u32 battler)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
//These Functions can prompt switch to generic pary members
|
//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))
|
if (ShouldSwitchIfAllBadMoves(battler))
|
||||||
return TRUE;
|
return TRUE;
|
||||||
if (ShouldSwitchIfAbilityBenefit(battler))
|
if (ShouldSwitchIfAbilityBenefit(battler))
|
||||||
return TRUE;
|
return TRUE;
|
||||||
if (HasBadOdds(battler))
|
if (HasBadOdds(battler))
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
if (ShouldSwitchIfEncored(battler))
|
||||||
|
return TRUE;
|
||||||
|
if (AreAttackingStatsLowered(battler))
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
//Removing switch capabilites under specific conditions
|
//Removing switch capabilites under specific conditions
|
||||||
//These Functions prevent the "FindMonWithFlagsAndSuperEffective" from getting out of hand.
|
//These Functions prevent the "FindMonWithFlagsAndSuperEffective" from getting out of hand.
|
||||||
|
@ -1108,7 +1262,7 @@ static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon
|
||||||
{
|
{
|
||||||
// Stealth Rock
|
// Stealth Rock
|
||||||
if ((hazardFlags & SIDE_STATUS_STEALTH_ROCK) && heldItemEffect != HOLD_EFFECT_HEAVY_DUTY_BOOTS)
|
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
|
// Spikes
|
||||||
if ((hazardFlags & SIDE_STATUS_SPIKES) && IsMonGrounded(heldItemEffect, ability, defType1, defType2))
|
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); }
|
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