AI can use Z-status moves (#7666)

This commit is contained in:
surskitty 2025-09-07 12:00:02 -04:00 committed by GitHub
parent cb2754a186
commit c3dec7d030
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 225 additions and 9 deletions

View File

@ -110,4 +110,8 @@
#define POWER_SPLIT_ALLY_PERCENTAGE 150
#define POWER_SPLIT_ENEMY_PERCENTAGE 50
// HP thresholds to use a status z-move.
#define Z_EFFECT_FOLLOW_ME_THRESHOLD 30
#define Z_EFFECT_RESTORE_HP_THRESHOLD 60
#endif // GUARD_CONFIG_AI_H

View File

@ -1985,7 +1985,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10);
if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY)
ADJUST_SCORE(-10);
else if (aiData->hpPercents[battlerAtk] <= 60)
else if (aiData->hpPercents[battlerAtk] <= 60 && (GetActiveGimmick(battlerAtk) != GIMMICK_Z_MOVE || GetMoveZEffect(move) == Z_EFFECT_NONE))
ADJUST_SCORE(-10);
break;
case EFFECT_FUTURE_SIGHT:
@ -2760,6 +2760,9 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_HOLD_HANDS:
case EFFECT_CELEBRATE:
case EFFECT_HAPPY_HOUR:
case EFFECT_LAST_RESORT:
if (gBattleStruct->gimmick.usableGimmick[battlerAtk] == GIMMICK_Z_MOVE && ShouldUseZMove(battlerAtk, battlerDef, move))
break;
ADJUST_SCORE(-10);
break;
case EFFECT_INSTRUCT:
@ -4443,7 +4446,8 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
case EFFECT_HOLD_HANDS:
case EFFECT_CELEBRATE:
case EFFECT_HAPPY_HOUR:
//todo - check z splash, z celebrate, z happy hour (lol)
if (gBattleStruct->gimmick.usableGimmick[battlerAtk] == GIMMICK_Z_MOVE && ShouldUseZMove(battlerAtk, battlerDef, move))
ADJUST_SCORE(BEST_EFFECT);
break;
case EFFECT_TELEPORT: // Either remove or add better logic
if (!(gBattleTypeFlags & BATTLE_TYPE_TRAINER) || !IsOnPlayerSide(battlerAtk))

View File

@ -4847,15 +4847,97 @@ bool32 AI_MoveMakesContact(u32 ability, enum ItemHoldEffect holdEffect, u32 move
bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove)
{
// simple logic. just upgrades chosen move to z move if possible, unless regular move would kill opponent
if ((IsDoubleBattle()) && battlerDef == BATTLE_PARTNER(battlerAtk))
if ((IsDoubleBattle()) && battlerDef == BATTLE_PARTNER(battlerAtk) && !(GetBattlerMoveTargetType(battlerAtk, chosenMove) & MOVE_TARGET_ALLY))
return FALSE; // don't use z move on partner
if (HasTrainerUsedGimmick(battlerAtk, GIMMICK_Z_MOVE))
return FALSE; // can't use z move twice
if (IsViableZMove(battlerAtk, chosenMove))
{
uq4_12_t effectiveness;
u32 zMove = GetUsableZMove(battlerAtk, chosenMove);
if (IsBattleMoveStatus(chosenMove))
{
u8 zEffect = GetMoveZEffect(chosenMove);
enum StatChange statChange = 0;
if (zEffect == Z_EFFECT_CURSE)
{
if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GHOST))
zEffect = Z_EFFECT_RECOVER_HP;
else
zEffect = Z_EFFECT_ATK_UP_1;
}
switch (zEffect)
{
case Z_EFFECT_NONE:
return FALSE;
case Z_EFFECT_RESET_STATS:
if (CountNegativeStatStages(battlerAtk) > 1)
return TRUE;
break;
case Z_EFFECT_ALL_STATS_UP_1:
if (AreBattlersStatsMaxed(battlerAtk))
return FALSE;
return IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK) > 0
|| IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK) > 0;
break;
case Z_EFFECT_BOOST_CRITS:
return TRUE;
case Z_EFFECT_FOLLOW_ME:
return HasPartnerIgnoreFlags(battlerAtk) && (GetHealthPercentage(battlerAtk) <= Z_EFFECT_FOLLOW_ME_THRESHOLD || GetBestNoOfHitsToKO(battlerDef, battlerAtk, AI_DEFENDING) == 1);
break;
case Z_EFFECT_RECOVER_HP:
return gAiLogicData->hpPercents[battlerAtk] <= Z_EFFECT_RESTORE_HP_THRESHOLD;
case Z_EFFECT_RESTORE_REPLACEMENT_HP:
break;
case Z_EFFECT_ACC_UP_1:
case Z_EFFECT_ACC_UP_2:
case Z_EFFECT_ACC_UP_3:
statChange = STAT_CHANGE_ACC;
break;
case Z_EFFECT_EVSN_UP_1:
case Z_EFFECT_EVSN_UP_2:
case Z_EFFECT_EVSN_UP_3:
statChange = STAT_CHANGE_EVASION;
break;
case Z_EFFECT_ATK_UP_1:
case Z_EFFECT_DEF_UP_1:
case Z_EFFECT_SPD_UP_1:
case Z_EFFECT_SPATK_UP_1:
case Z_EFFECT_SPDEF_UP_1:
statChange = STAT_CHANGE_ATK + zEffect - Z_EFFECT_ATK_UP_1;
break;
case Z_EFFECT_ATK_UP_2:
case Z_EFFECT_DEF_UP_2:
case Z_EFFECT_SPD_UP_2:
case Z_EFFECT_SPATK_UP_2:
case Z_EFFECT_SPDEF_UP_2:
statChange = STAT_CHANGE_ATK_2 + zEffect - Z_EFFECT_ATK_UP_2;
break;
case Z_EFFECT_ATK_UP_3:
case Z_EFFECT_DEF_UP_3:
case Z_EFFECT_SPD_UP_3:
case Z_EFFECT_SPATK_UP_3:
case Z_EFFECT_SPDEF_UP_3:
statChange = STAT_CHANGE_ATK_2 + zEffect - Z_EFFECT_ATK_UP_3;
break;
default:
return FALSE;
}
if (statChange != 0 && IncreaseStatUpScore(battlerAtk, battlerDef, statChange) > 0)
return TRUE;
}
else if (GetMoveEffect(zMove) == EFFECT_EXTREME_EVOBOOST)
{
return (!AreBattlersStatsMaxed(battlerAtk) && (IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK_2) || IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK_2)));
}
else if (!IsBattleMoveStatus(chosenMove) && IsBattleMoveStatus(zMove))
return FALSE;
uq4_12_t effectiveness;
struct SimulatedDamage dmg;
if (gBattleMons[battlerDef].ability == ABILITY_DISGUISE
@ -4867,11 +4949,6 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove)
&& gBattleMons[battlerDef].species == SPECIES_EISCUE_ICE && IsBattleMovePhysical(chosenMove))
return FALSE; // Don't waste a Z-Move busting Ice Face
if (IsBattleMoveStatus(chosenMove) && !IsBattleMoveStatus(zMove))
return FALSE;
else if (!IsBattleMoveStatus(chosenMove) && IsBattleMoveStatus(zMove))
return FALSE;
dmg = AI_CalcDamageSaveBattlers(chosenMove, battlerAtk, battlerDef, &effectiveness, NO_GIMMICK, NO_GIMMICK);
if (!IsBattleMoveStatus(chosenMove) && dmg.minimum >= gBattleMons[battlerDef].hp)

View File

@ -32,3 +32,94 @@ AI_SINGLE_BATTLE_TEST("AI does not use damaging Z-moves if the player would fain
TURN { EXPECT_MOVE(opponent, MOVE_QUICK_ATTACK, gimmick: GIMMICK_NONE); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Splash")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT );
ASSUME(GetMoveType(MOVE_QUICK_ATTACK) == TYPE_NORMAL);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); Moves(MOVE_POUND, MOVE_SPLASH); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_SPLASH, gimmick: GIMMICK_Z_MOVE);
SCORE_GT_VAL(opponent, MOVE_SPLASH, AI_SCORE_DEFAULT); }
TURN { EXPECT_MOVE(opponent, MOVE_POUND, gimmick: GIMMICK_NONE);
SCORE_EQ_VAL(opponent, MOVE_SPLASH, 90); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Happy Hour")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT );
ASSUME(GetMoveType(MOVE_QUICK_ATTACK) == TYPE_NORMAL);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); Moves(MOVE_POUND, MOVE_HAPPY_HOUR); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_HAPPY_HOUR, gimmick: GIMMICK_Z_MOVE);
SCORE_GT_VAL(opponent, MOVE_HAPPY_HOUR, AI_SCORE_DEFAULT); }
TURN { EXPECT_MOVE(opponent, MOVE_POUND, gimmick: GIMMICK_NONE);
SCORE_EQ_VAL(opponent, MOVE_HAPPY_HOUR, 90); }
}
}
// Last Resort itself is missing logic!
AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Extreme Evoboost")
{
KNOWN_FAILING;
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT );
ASSUME(GetMoveType(MOVE_QUICK_ATTACK) == TYPE_NORMAL);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_EEVEE) { Item(ITEM_EEVIUM_Z); Moves(MOVE_POUND, MOVE_LAST_RESORT); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_LAST_RESORT, gimmick: GIMMICK_Z_MOVE); }
TURN { EXPECT_MOVE(opponent, MOVE_POUND, gimmick: GIMMICK_NONE);
SCORE_EQ_VAL(opponent, MOVE_LAST_RESORT, 80); }
// Uncomment when Last Resort works correctly.
// TURN { EXPECT_MOVE(opponent, MOVE_LAST_RESORT, gimmick: GIMMICK_NONE); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- 10,000,000 Volt Thunderbolt")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT );
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_PIKACHU_PARTNER) { Item(ITEM_PIKASHUNIUM_Z); Moves(MOVE_THUNDERBOLT); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_THUNDERBOLT, gimmick: GIMMICK_Z_MOVE); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Destiny Bond is not used in singles")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT );
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_GHOSTIUM_Z); Moves(MOVE_DESTINY_BOND); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_DESTINY_BOND, gimmick: GIMMICK_NONE); }
}
}
AI_DOUBLE_BATTLE_TEST("AI uses Z-Moves -- Z-Destiny Bond is used when about to die")
{
u32 currentHP;
PARAMETRIZE { currentHP = 1; }
PARAMETRIZE { currentHP = 500; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT );
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_POUND); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { HP(currentHP); Item(ITEM_GHOSTIUM_Z); Moves(MOVE_DESTINY_BOND); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (currentHP == 1)
TURN { EXPECT_MOVE(opponentLeft, MOVE_DESTINY_BOND, gimmick: GIMMICK_Z_MOVE); }
else
TURN { EXPECT_MOVE(opponentLeft, MOVE_DESTINY_BOND, gimmick: GIMMICK_NONE); }
}
}

View File

@ -114,3 +114,43 @@ SINGLE_BATTLE_TEST("Last Resort works with Sleep Talk")
HP_BAR(opponent);
}
}
AI_SINGLE_BATTLE_TEST("AI uses Last Resort - 2 moves")
{
KNOWN_FAILING;
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_LAST_RESORT, MOVE_SCRATCH); }
} WHEN {
TURN { NOT_EXPECT_MOVE(opponent, MOVE_LAST_RESORT); }
TURN { EXPECT_MOVE(opponent, MOVE_LAST_RESORT); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Last Resort - 3 moves")
{
KNOWN_FAILING;
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_LAST_RESORT, MOVE_QUICK_ATTACK, MOVE_SCRATCH); }
} WHEN {
TURN { NOT_EXPECT_MOVE(opponent, MOVE_LAST_RESORT); }
TURN { NOT_EXPECT_MOVE(opponent, MOVE_LAST_RESORT); }
TURN { EXPECT_MOVE(opponent, MOVE_LAST_RESORT); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Last Resort - 4 moves")
{
KNOWN_FAILING;
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_LAST_RESORT, MOVE_QUICK_ATTACK, MOVE_SCRATCH, MOVE_GUST); }
} WHEN {
TURN { NOT_EXPECT_MOVE(opponent, MOVE_LAST_RESORT); }
TURN { NOT_EXPECT_MOVE(opponent, MOVE_LAST_RESORT); }
TURN { NOT_EXPECT_MOVE(opponent, MOVE_LAST_RESORT); }
TURN { EXPECT_MOVE(opponent, MOVE_LAST_RESORT); }
}
}