Feature/ai/wide guard quick guard singles (#7086)

Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
This commit is contained in:
Emilia Daelman 2025-06-08 21:50:34 +02:00 committed by GitHub
parent 872bb8785b
commit f4ff5e6d26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 69 additions and 20 deletions

View File

@ -167,7 +167,8 @@ bool32 IsMoveRedirectionPrevented(u32 battlerAtk, u32 move, u32 atkAbility);
bool32 IsMoveEncouragedToHit(u32 battlerAtk, u32 battlerDef, u32 move);
bool32 IsHazardMove(u32 move);
bool32 IsTwoTurnNotSemiInvulnerableMove(u32 battlerAtk, u32 move);
void ProtectChecks(u32 battlerAtk, u32 battlerDef, u32 move, u32 predictedMove, s32 *score);
bool32 IsBattlerDamagedByStatus(u32 battler);
s32 ProtectChecks(u32 battlerAtk, u32 battlerDef, u32 move, u32 predictedMove);
bool32 ShouldSetSandstorm(u32 battler, u32 ability, enum ItemHoldEffect holdEffect);
bool32 ShouldSetHail(u32 battler, u32 ability, enum ItemHoldEffect holdEffect);
bool32 ShouldSetSnow(u32 battler, u32 ability, enum ItemHoldEffect holdEffect);

View File

@ -2202,7 +2202,17 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
switch (protectMethod)
{
case PROTECT_QUICK_GUARD:
if (GetMovePriority(predictedMove) <= 0)
{
ADJUST_SCORE(-10);
decreased = TRUE;
}
case PROTECT_WIDE_GUARD:
if(!(GetBattlerMoveTargetType(battlerAtk, predictedMove) & (MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_BOTH)))
{
ADJUST_SCORE(-10);
decreased = TRUE;
}
case PROTECT_CRAFTY_SHIELD:
if (!isDoubleBattle)
{
@ -4288,28 +4298,30 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
{
case PROTECT_QUICK_GUARD:
if (predictedMove != MOVE_NONE && GetMovePriority(predictedMove) > 0)
ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score);
{
ADJUST_SCORE(ProtectChecks(battlerAtk, battlerDef, move, predictedMove));
}
break;
case PROTECT_WIDE_GUARD:
if (predictedMove != MOVE_NONE && GetBattlerMoveTargetType(battlerDef, predictedMove) & (MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_BOTH))
{
ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score);
ADJUST_SCORE(ProtectChecks(battlerAtk, battlerDef, move, predictedMove));
}
else if (isDoubleBattle && GetBattlerMoveTargetType(BATTLE_PARTNER(battlerAtk), aiData->partnerMove) & MOVE_TARGET_FOES_AND_ALLY)
{
if (aiData->abilities[battlerAtk] != ABILITY_TELEPATHY)
ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score);
ADJUST_SCORE(ProtectChecks(battlerAtk, battlerDef, move, predictedMove));
}
break;
case PROTECT_CRAFTY_SHIELD:
if (predictedMove != MOVE_NONE && IsBattleMoveStatus(predictedMove) && !(GetBattlerMoveTargetType(battlerDef, predictedMove) & MOVE_TARGET_USER))
ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score);
ADJUST_SCORE(ProtectChecks(battlerAtk, battlerDef, move, predictedMove));
break;
case PROTECT_MAT_BLOCK:
if (gDisableStructs[battlerAtk].isFirstTurn && predictedMove != MOVE_NONE
&& !IsBattleMoveStatus(predictedMove) && !(GetBattlerMoveTargetType(battlerDef, predictedMove) & MOVE_TARGET_USER))
ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score);
ADJUST_SCORE(ProtectChecks(battlerAtk, battlerDef, move, predictedMove));
break;
case PROTECT_KINGS_SHIELD:
if (aiData->abilities[battlerAtk] == ABILITY_STANCE_CHANGE //Special logic for Aegislash
@ -4321,7 +4333,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
}
//fallthrough
default: // protect
ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score);
ADJUST_SCORE(ProtectChecks(battlerAtk, battlerDef, move, predictedMove));
break;
}
break;

View File

@ -1893,8 +1893,19 @@ bool32 ShouldSetSnow(u32 battler, u32 ability, enum ItemHoldEffect holdEffect)
return FALSE;
}
void ProtectChecks(u32 battlerAtk, u32 battlerDef, u32 move, u32 predictedMove, s32 *score)
bool32 IsBattlerDamagedByStatus(u32 battler)
{
return gBattleMons[battler].status1 & (STATUS1_BURN | STATUS1_FROSTBITE | STATUS1_POISON | STATUS1_TOXIC_POISON)
|| gBattleMons[battler].status2 & (STATUS2_WRAPPED | STATUS2_NIGHTMARE | STATUS2_CURSED)
|| gStatuses3[battler] & (STATUS3_PERISH_SONG | STATUS3_LEECHSEED)
|| gStatuses4[battler] & (STATUS4_SALT_CURE)
|| gSideStatuses[GetBattlerSide(battler)] & (SIDE_STATUS_SEA_OF_FIRE | SIDE_STATUS_DAMAGE_NON_TYPES);
}
s32 ProtectChecks(u32 battlerAtk, u32 battlerDef, u32 move, u32 predictedMove)
{
s32 score = 0;
// TODO more sophisticated logic
u32 uses = gDisableStructs[battlerAtk].protectUses;
@ -1907,29 +1918,29 @@ void ProtectChecks(u32 battlerAtk, u32 battlerDef, u32 move, u32 predictedMove,
if (uses == 0)
{
if (predictedMove != MOVE_NONE && predictedMove != 0xFFFF && !IsBattleMoveStatus(predictedMove))
ADJUST_SCORE_PTR(DECENT_EFFECT);
score += DECENT_EFFECT;
else if (Random() % 256 < 100)
ADJUST_SCORE_PTR(WEAK_EFFECT);
score += WEAK_EFFECT;
}
else
{
if (IsDoubleBattle())
ADJUST_SCORE_PTR(-(2 * min(uses, 3)));
score -= (2 * min(uses, 3));
else
ADJUST_SCORE_PTR(-(min(uses, 3)));
score -= (min(uses, 3));
}
if (gBattleMons[battlerAtk].status1 & (STATUS1_PSN_ANY | STATUS1_BURN | STATUS1_FROSTBITE)
|| gBattleMons[battlerAtk].status2 & (STATUS2_CURSED | STATUS2_INFATUATION)
|| gStatuses3[battlerAtk] & (STATUS3_PERISH_SONG | STATUS3_LEECHSEED | STATUS3_YAWN))
if (IsBattlerDamagedByStatus(battlerAtk))
{
ADJUST_SCORE_PTR(-1);
score -= 1;
}
if (gBattleMons[battlerDef].status1 & STATUS1_TOXIC_POISON
|| gBattleMons[battlerDef].status2 & (STATUS2_CURSED | STATUS2_INFATUATION)
|| gStatuses3[battlerDef] & (STATUS3_PERISH_SONG | STATUS3_LEECHSEED | STATUS3_YAWN))
ADJUST_SCORE_PTR(DECENT_EFFECT);
if (IsBattlerDamagedByStatus(battlerDef))
{
score += DECENT_EFFECT;
}
return score;
}
// stat stages

View File

@ -1,6 +1,9 @@
#include "constants/battle_ai.h"
#include "constants/moves.h"
#include "global.h"
#include "test/battle.h"
#include "battle_ai_util.h"
#include "test/test.h"
ASSUMPTIONS
{
@ -253,3 +256,25 @@ AI_SINGLE_BATTLE_TEST("AI prioritizes Pursuit if it would KO opponent")
TURN { SWITCH(player, 1); EXPECT_MOVE(opponent, MOVE_PURSUIT); SEND_OUT(player, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Quick Guard against Quick Attack when opponent would die of poison")
{
GIVEN {
PLAYER(SPECIES_ZUBAT) { Moves(MOVE_QUICK_ATTACK); Status1(STATUS1_TOXIC_POISON); }
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE);
OPPONENT(SPECIES_RATTATA) { Moves(MOVE_QUICK_GUARD, MOVE_TACKLE); }
} WHEN {
TURN { MOVE(player, MOVE_QUICK_ATTACK); EXPECT_MOVE(opponent, MOVE_QUICK_GUARD); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Wide Guard against Earthquake when opponent would die of poison")
{
GIVEN {
PLAYER(SPECIES_ZUBAT) { Moves(MOVE_EARTHQUAKE); Status1(STATUS1_TOXIC_POISON); }
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE);
OPPONENT(SPECIES_RATTATA) { Moves(MOVE_WIDE_GUARD, MOVE_TACKLE); }
} WHEN {
TURN { MOVE(player, MOVE_EARTHQUAKE); EXPECT_MOVE(opponent, MOVE_WIDE_GUARD); }
}
}