Z Status move handling: Conversion, Detect, Nature Power, Transform (#7721)

This commit is contained in:
surskitty 2025-09-24 13:55:29 -04:00 committed by GitHub
parent 63d04947e4
commit 82ab4fe98d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 166 additions and 36 deletions

View File

@ -112,6 +112,7 @@
// HP thresholds to use a status z-move.
#define Z_EFFECT_FOLLOW_ME_THRESHOLD 30
#define Z_EFFECT_RESTORE_HP_THRESHOLD 60
#define Z_EFFECT_RESTORE_HP_LOWER_THRESHOLD ENABLE_RECOVERY_THRESHOLD // threshold used for moves you could conceivably use more than once
#define Z_EFFECT_RESTORE_HP_HIGHER_THRESHOLD 90 // these moves are one-time use or drop your HP
#endif // GUARD_CONFIG_AI_H

View File

@ -1986,7 +1986,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 && (GetActiveGimmick(battlerAtk) != GIMMICK_Z_MOVE || GetMoveZEffect(move) == Z_EFFECT_NONE))
else if (aiData->hpPercents[battlerAtk] <= 60 && !IsConsideringZMove(battlerAtk, battlerDef, move))
ADJUST_SCORE(-10);
break;
case EFFECT_FUTURE_SIGHT:
@ -4322,7 +4322,13 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru
break;
case EFFECT_CONVERSION:
if (!IS_BATTLER_OF_TYPE(battlerAtk, GetMoveType(gBattleMons[battlerAtk].moves[0])))
{
ADJUST_SCORE(WEAK_EFFECT);
if (aiData->abilities[battlerAtk] == ABILITY_ADAPTABILITY)
ADJUST_SCORE(WEAK_EFFECT);
if (IsConsideringZMove(battlerAtk, battlerDef, move))
ADJUST_SCORE(BEST_EFFECT);
}
break;
case EFFECT_SWALLOW:
if (gDisableStructs[battlerAtk].stockpileCounter == 0)

View File

@ -4920,12 +4920,16 @@ bool32 AI_MoveMakesContact(u32 ability, enum ItemHoldEffect holdEffect, u32 move
bool32 IsConsideringZMove(u32 battlerAtk, u32 battlerDef, u32 move)
{
if (GetMovePower(move) == 0 && GetMoveZEffect(move) == Z_EFFECT_NONE)
return FALSE;
return gBattleStruct->gimmick.usableGimmick[battlerAtk] == GIMMICK_Z_MOVE && ShouldUseZMove(battlerAtk, battlerDef, move);
}
//TODO - this could use some more sophisticated logic
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) && !(GetBattlerMoveTargetType(battlerAtk, chosenMove) & MOVE_TARGET_ALLY))
return FALSE; // don't use z move on partner
@ -4934,6 +4938,39 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove)
if (IsViableZMove(battlerAtk, chosenMove))
{
enum BattleMoveEffects baseEffect = GetMoveEffect(chosenMove);
bool32 isEager = FALSE; // more likely to use a z move than typical
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
bool32 isSlower = AI_IsSlower(battlerAtk, battlerDef, chosenMove, predictedMoveSpeedCheck, CONSIDER_PRIORITY);
switch (baseEffect)
{
case EFFECT_BELLY_DRUM:
case EFFECT_FILLET_AWAY:
if (isSlower)
return TRUE;
isEager = TRUE;
break;
case EFFECT_PROTECT:
if (HasDamagingMoveOfType(battlerAtk, GetMoveType(gMovesInfo[chosenMove].type)))
return FALSE;
else
isEager = TRUE;
break;
case EFFECT_TELEPORT:
isEager = TRUE;
break;
case EFFECT_TRANSFORM:
if (IsBattlerTrapped(battlerDef, battlerAtk) && !HasDamagingMoveOfType(battlerDef, GetMoveType(gMovesInfo[chosenMove].type)))
return TRUE;
if (isSlower)
isEager = TRUE;
break;
default:
break;
}
u32 zMove = GetUsableZMove(battlerAtk, chosenMove);
if (IsBattleMoveStatus(chosenMove))
@ -4952,7 +4989,9 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove)
switch (zEffect)
{
case Z_EFFECT_NONE:
return FALSE;
if (GetMovePower(chosenMove) == 0)
return FALSE;
break;
case Z_EFFECT_RESET_STATS:
if (CountNegativeStatStages(battlerAtk) > 1)
return TRUE;
@ -4965,7 +5004,11 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove)
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;
if (GetBestNoOfHitsToKO(battlerDef, battlerAtk, AI_DEFENDING) == 1 && GetHealthPercentage(battlerAtk) > Z_EFFECT_RESTORE_HP_HIGHER_THRESHOLD)
return TRUE;
if (isEager)
return GetHealthPercentage(battlerAtk) <= Z_EFFECT_RESTORE_HP_HIGHER_THRESHOLD;
return GetHealthPercentage(battlerAtk) <= Z_EFFECT_RESTORE_HP_LOWER_THRESHOLD;
case Z_EFFECT_RESTORE_REPLACEMENT_HP:
break;
case Z_EFFECT_ACC_UP_1:
@ -5003,8 +5046,9 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove)
return FALSE;
}
if (statChange != 0 && IncreaseStatUpScore(battlerAtk, battlerDef, statChange) > 0)
if (statChange != 0 && (isEager || IncreaseStatUpScore(battlerAtk, battlerDef, statChange) > 0))
return TRUE;
}
else if (GetMoveEffect(zMove) == EFFECT_EXTREME_EVOBOOST)
{

View File

@ -32,37 +32,6 @@ 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); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Extreme Evoboost")
{
GIVEN {
@ -88,6 +57,25 @@ AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- 10,000,000 Volt Thunderbolt")
TURN { EXPECT_MOVE(opponent, MOVE_THUNDERBOLT, gimmick: GIMMICK_Z_MOVE); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Conversion")
{
u32 ability;
PARAMETRIZE { ability = ABILITY_NONE; }
PARAMETRIZE { ability = ABILITY_OPPORTUNIST; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT );
ASSUME(GetMoveType(MOVE_CONVERSION) == TYPE_NORMAL);
PLAYER(SPECIES_WOBBUFFET) { Ability(ability); }
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); Ability(ABILITY_ADAPTABILITY); Moves(MOVE_THUNDERBOLT, MOVE_CONVERSION); }
} WHEN {
if (ability == ABILITY_OPPORTUNIST)
TURN { EXPECT_MOVE(opponent, MOVE_CONVERSION, gimmick: GIMMICK_NONE); }
else
TURN { EXPECT_MOVE(opponent, MOVE_CONVERSION, gimmick: GIMMICK_Z_MOVE); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Destiny Bond is not used in singles")
{
@ -120,3 +108,94 @@ AI_DOUBLE_BATTLE_TEST("AI uses Z-Moves -- Z-Destiny Bond is used when about to d
}
}
AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Detect")
{
u32 move;
PARAMETRIZE { move = MOVE_THUNDERBOLT; }
PARAMETRIZE { move = MOVE_CLOSE_COMBAT; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE );
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_FAKE_OUT); }
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_FIGHTINIUM_Z); Moves(MOVE_DETECT, move); }
} WHEN {
if (move == MOVE_CLOSE_COMBAT)
TURN { EXPECT_MOVE(opponent, MOVE_DETECT, gimmick: GIMMICK_NONE); }
else
TURN { EXPECT_MOVE(opponent, MOVE_DETECT, gimmick: GIMMICK_Z_MOVE); }
}
}
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); }
}
}
TO_DO_BATTLE_TEST("TODO: AI uses Z-Moves -- Z-Haze")
TO_DO_BATTLE_TEST("TODO: AI uses Z-Moves -- Z-Mirror Move")
AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Nature Power")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); Moves(MOVE_NATURE_POWER, MOVE_HEADBUTT); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_NATURE_POWER, gimmick: GIMMICK_Z_MOVE); }
}
}
// Requires handling for Wish passing/Healing Wish/other ways to determine what pokemon to heal via switching into.
TO_DO_BATTLE_TEST("TODO: AI uses Z-Moves -- Z-Parting Shot")
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); }
}
}
TO_DO_BATTLE_TEST("TODO: AI uses Z-Moves -- Z-Tailwind")
AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Transform")
{
u32 currentHP, move;
PARAMETRIZE { currentHP = 1; move = MOVE_HEADBUTT; }
PARAMETRIZE { currentHP = 1; move = MOVE_THUNDERBOLT; }
PARAMETRIZE { currentHP = 500; move = MOVE_HEADBUTT; }
PARAMETRIZE { currentHP = 500; move = MOVE_THUNDERBOLT; }
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, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET) { HP(currentHP); Item(ITEM_NORMALIUM_Z); Moves(MOVE_TRANSFORM); }
} WHEN {
if (currentHP == 1 || move == MOVE_THUNDERBOLT)
TURN { EXPECT_MOVE(opponent, MOVE_TRANSFORM, gimmick: GIMMICK_Z_MOVE); }
else
TURN { EXPECT_MOVE(opponent, MOVE_TRANSFORM, gimmick: GIMMICK_NONE); }
}
}
TO_DO_BATTLE_TEST("TODO: AI uses Z-Moves -- Z-Trick Room")