Smarter Focus Punch and Substitute (#4952)

* Smarter Focus Punch

* Smarter Substitute, review feedback

* Use HasAnyKnownMove instead of isFirstTurn

* When are we removing agbcc again

* Use HasMoveEffect
This commit is contained in:
Pawkkie 2024-07-14 02:29:27 -04:00 committed by GitHub
parent f6d2b2861a
commit c721f1b04a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 83 additions and 12 deletions

View file

@ -42,6 +42,7 @@ s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 moveConsidered);
bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk);
u32 NoOfHitsForTargetToFaintAI(u32 battlerDef, u32 battlerAtk);
u32 GetBestDmgMoveFromBattler(u32 battlerAtk, u32 battlerDef);
u32 GetBestDmgFromBattler(u32 battler, u32 battlerTarget);
bool32 CanTargetMoveFaintAi(u32 move, u32 battlerDef, u32 battlerAtk, u32 nHits);
bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dmgMod);
s32 AI_DecideKnownAbilityForTurn(u32 battlerId);
@ -115,6 +116,7 @@ bool32 HasMoveWithAdditionalEffect(u32 battlerId, u32 moveEffect);
bool32 HasMoveWithCriticalHitChance(u32 battlerId);
bool32 HasMoveWithMoveEffectExcept(u32 battlerId, u32 moveEffect, u32 exception);
bool32 HasMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef, u32 accCheck, bool32 ignoreStatus, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect);
bool32 HasAnyKnownMove(u32 battlerId);
bool32 IsAromaVeilProtectedMove(u32 move);
bool32 IsNonVolatileStatusMoveEffect(u32 moveEffect);
bool32 IsStatLoweringMoveEffect(u32 moveEffect);

View file

@ -3564,11 +3564,17 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
IncreaseParalyzeScore(battlerAtk, battlerDef, move, &score);
break;
case EFFECT_SUBSTITUTE:
ADJUST_SCORE(GOOD_EFFECT);
if (HasAnyKnownMove(battlerDef) && GetBestDmgFromBattler(battlerDef, battlerAtk) < gBattleMons[battlerAtk].maxHP / 4)
ADJUST_SCORE(GOOD_EFFECT);
if (gStatuses3[battlerDef] & STATUS3_PERISH_SONG)
ADJUST_SCORE(GOOD_EFFECT);
if (gBattleMons[battlerDef].status1 & (STATUS1_BURN | STATUS1_PSN_ANY | STATUS1_FROSTBITE))
if (gBattleMons[battlerDef].status1 & STATUS1_SLEEP)
ADJUST_SCORE(GOOD_EFFECT);
else if (gBattleMons[battlerDef].status1 & (STATUS1_BURN | STATUS1_PSN_ANY | STATUS1_FROSTBITE))
ADJUST_SCORE(DECENT_EFFECT);
// TODO:
// if (IsPredictedToSwitch(battlerDef, battlerAtk)
// ADJUST_SCORE(DECENT_EFFECT);
if (HasMoveEffect(battlerDef, EFFECT_SLEEP)
|| HasMoveEffect(battlerDef, EFFECT_TOXIC)
|| HasMoveEffect(battlerDef, EFFECT_POISON)
@ -4462,15 +4468,6 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
else if (ShouldRecover(battlerAtk, battlerDef, move, 50))
ADJUST_SCORE(DECENT_EFFECT);
break;
case EFFECT_FOCUS_PUNCH:
if (!isDoubleBattle && effectiveness > AI_EFFECTIVENESS_x0_5)
{
if (IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]))
ADJUST_SCORE(DECENT_EFFECT);
if (gBattleMons[battlerDef].status2 & (STATUS2_INFATUATION | STATUS2_CONFUSION))
ADJUST_SCORE(DECENT_EFFECT);
}
break;
case EFFECT_ENDEAVOR:
if (AI_IsSlower(battlerAtk, battlerDef, move) && !CanTargetFaintAi(battlerDef, battlerAtk))
ADJUST_SCORE(DECENT_EFFECT);

View file

@ -493,6 +493,16 @@ bool32 IsDamageMoveUnusable(u32 move, u32 battlerAtk, u32 battlerDef)
if (!gDisableStructs[battlerAtk].isFirstTurn)
return TRUE;
break;
case EFFECT_FOCUS_PUNCH:
if (HasDamagingMove(battlerDef) && !((gBattleMons[battlerAtk].status2 & STATUS2_SUBSTITUTE)
|| IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef])
|| gBattleMons[battlerDef].status2 & (STATUS2_INFATUATION | STATUS2_CONFUSION)))
// TODO: || IsPredictedToSwitch(battlerDef, battlerAtk)
return TRUE;
// If AI could Sub and doesn't have a Sub, don't Punch yet
if (HasMoveEffect(battlerAtk, EFFECT_SUBSTITUTE) && !(gBattleMons[battlerAtk].status2 & STATUS2_SUBSTITUTE))
return TRUE;
break;
}
return FALSE;
@ -1106,6 +1116,27 @@ u32 GetBestDmgMoveFromBattler(u32 battlerAtk, u32 battlerDef)
return move;
}
u32 GetBestDmgFromBattler(u32 battler, u32 battlerTarget)
{
u32 i;
u32 bestDmg = 0;
u32 unusable = AI_DATA->moveLimitations[battler];
u16 *moves = GetMovesArray(battler);
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moves[i] != MOVE_NONE
&& moves[i] != MOVE_UNAVAILABLE
&& !(unusable & gBitTable[i])
&& bestDmg < AI_DATA->simulatedDmg[battler][battlerTarget][i].expected)
{
bestDmg = AI_DATA->simulatedDmg[battler][battlerTarget][i].expected;
}
}
return bestDmg;
}
// Check if AI mon has the means to faint the target with any of its moves.
// If numHits > 1, check if the target will be KO'ed by that number of hits (ignoring healing effects)
bool32 CanAIFaintTarget(u32 battlerAtk, u32 battlerDef, u32 numHits)
@ -1998,6 +2029,20 @@ bool32 HasMove(u32 battlerId, u32 move)
return FALSE;
}
bool32 HasAnyKnownMove(u32 battlerId)
{
s32 i;
u16 *moves = GetMovesArray(battlerId);
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moves[i] != MOVE_NONE)
return TRUE;
}
return FALSE;
}
bool32 HasMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef, u32 accCheck, bool32 ignoreStatus, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect)
{
s32 i;

View file

@ -74,3 +74,28 @@ DOUBLE_BATTLE_TEST("Focus Punch activation is based on Speed")
MESSAGE("Foe Wobbuffet lost its focus and couldn't move!");
}
}
AI_SINGLE_BATTLE_TEST("AI won't use Focus Punch if it predicts a damaging move")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_MAGNEZONE) { Moves(MOVE_THUNDER_WAVE, MOVE_FLASH_CANNON, MOVE_DISCHARGE, MOVE_TRI_ATTACK); }
OPPONENT(SPECIES_BRELOOM) { Moves(MOVE_FOCUS_PUNCH, MOVE_SEED_BOMB); }
} WHEN {
TURN { MOVE(player, MOVE_DISCHARGE); EXPECT_MOVE(opponent, MOVE_FOCUS_PUNCH); }
TURN { MOVE(player, MOVE_DISCHARGE); EXPECT_MOVE(opponent, MOVE_SEED_BOMB); }
}
}
AI_SINGLE_BATTLE_TEST("AI will Incapacitate -> Substitute -> Focus Punch if able")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_MAGNEZONE) { Moves(MOVE_THUNDER_WAVE, MOVE_FLASH_CANNON, MOVE_DISCHARGE, MOVE_TRI_ATTACK); }
OPPONENT(SPECIES_BRELOOM) { Moves(MOVE_SPORE, MOVE_FOCUS_PUNCH, MOVE_SUBSTITUTE, MOVE_SEED_BOMB); }
} WHEN {
TURN { MOVE(player, MOVE_DISCHARGE); EXPECT_MOVE(opponent, MOVE_SPORE); }
TURN { MOVE(player, MOVE_DISCHARGE); EXPECT_MOVE(opponent, MOVE_SUBSTITUTE); }
TURN { MOVE(player, MOVE_DISCHARGE); EXPECT_MOVE(opponent, MOVE_FOCUS_PUNCH); }
}
}

View file

@ -74,13 +74,14 @@ SINGLE_BATTLE_TEST("Tidy Up removes Substitute")
}
}
AI_SINGLE_BATTLE_TEST("AI prefers to keep it's substitute over removing hazards if target is slower")
AI_SINGLE_BATTLE_TEST("AI prefers to keep its substitute over removing hazards if target is slower")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { Speed(50); Status1(STATUS1_PARALYSIS); Moves(MOVE_SLEEP_POWDER, MOVE_STEALTH_ROCK, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(100); Moves(MOVE_BITE, MOVE_TACKLE, MOVE_SUBSTITUTE, MOVE_TIDY_UP); }
} WHEN {
TURN { MOVE(player, MOVE_STEALTH_ROCK); EXPECT_MOVE(opponent, MOVE_TIDY_UP); }
TURN { MOVE(player, MOVE_STEALTH_ROCK); EXPECT_MOVE(opponent, MOVE_SUBSTITUTE); }
TURN { EXPECT_MOVE(opponent, MOVE_BITE); }
}
@ -93,6 +94,7 @@ AI_SINGLE_BATTLE_TEST("AI will try to remove hazards if slower then target even
PLAYER(SPECIES_WOBBUFFET) { Speed(100); Status1(STATUS1_BURN); Moves(MOVE_SLEEP_POWDER, MOVE_STEALTH_ROCK, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Moves(MOVE_BITE, MOVE_TACKLE, MOVE_SUBSTITUTE, MOVE_TIDY_UP); }
} WHEN {
TURN { MOVE(player, MOVE_STEALTH_ROCK); EXPECT_MOVE(opponent, MOVE_TIDY_UP); }
TURN { MOVE(player, MOVE_STEALTH_ROCK); EXPECT_MOVE(opponent, MOVE_SUBSTITUTE); }
TURN { EXPECT_MOVE(opponent, MOVE_TIDY_UP); }
}