From 424c127b8bab529ef7b8834e3d8b74c9281bb672 Mon Sep 17 00:00:00 2001 From: surskitty Date: Thu, 10 Jul 2025 16:08:14 -0400 Subject: [PATCH] AI Tests + accompanying bugfixes for Skill Swap, Worry Seed, weather setting in double battles, and Discharging into an ally's lightningrod (#7297) --- include/battle_ai_main.h | 3 +- src/battle_ai_util.c | 10 ++-- test/battle/ai/ai_check_viability.c | 37 +++++++++++-- test/battle/ai/ai_doubles.c | 84 +++++++++++++++++++++++------ 4 files changed, 111 insertions(+), 23 deletions(-) diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index 6c58063350..547395d480 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -39,9 +39,10 @@ enum AIScore DECENT_EFFECT = 2, GOOD_EFFECT = 3, BEST_EFFECT = 4, + PERFECT_EFFECT = 10, BAD_EFFECT = -1, AWFUL_EFFECT = -3, - WORST_EFFECT = -5 + WORST_EFFECT = -10 }; // AI_TryToFaint diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index f0defb570a..48403d4e11 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -5279,7 +5279,7 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData case ABILITY_POWER_SPOT: case ABILITY_VICTORY_STAR: if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battler)) && aiData->abilities[BATTLE_PARTNER(battler)] != ability) - return GOOD_EFFECT; + return BEST_EFFECT; break; case ABILITY_GUTS: if (HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL) && gBattleMons[battler].status1 & (STATUS1_CAN_MOVE)) @@ -5295,7 +5295,7 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData case ABILITY_VITAL_SPIRIT: if (HasMoveWithEffect(battler, EFFECT_REST)) return WORST_EFFECT; - return NO_INCREASE; + break; case ABILITY_INTIMIDATE: { u32 abilityDef = aiData->abilities[FOE(battler)]; @@ -5335,6 +5335,8 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData return WEAK_EFFECT; if (gBattleMons[battler].status1 & (STATUS1_TOXIC_POISON)) return BEST_EFFECT; + if (gBattleMons[battler].status1 & STATUS1_ANY) + return NO_INCREASE; break; // Also used to Simple Beam SIMPLE_BEAM. case ABILITY_SIMPLE: @@ -5354,11 +5356,13 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData return NO_INCREASE; } return GOOD_EFFECT; + case ABILITY_NONE: + return NO_INCREASE; default: break; } - return NO_INCREASE; + return WEAK_EFFECT; } u32 GetThinkingBattler(u32 battler) diff --git a/test/battle/ai/ai_check_viability.c b/test/battle/ai/ai_check_viability.c index 47cf2450a9..1407023ece 100644 --- a/test/battle/ai/ai_check_viability.c +++ b/test/battle/ai/ai_check_viability.c @@ -281,12 +281,22 @@ AI_SINGLE_BATTLE_TEST("AI uses Wide Guard against Earthquake when opponent would AI_SINGLE_BATTLE_TEST("AI uses Worry Seed against Rest") { + u32 move; + u64 aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT; + + PARAMETRIZE { move = MOVE_REST; } + PARAMETRIZE { move = MOVE_EXTREME_SPEED; } + PARAMETRIZE { move = MOVE_REST; aiFlags |= AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; } + PARAMETRIZE { move = MOVE_EXTREME_SPEED; aiFlags |= AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; } GIVEN { - PLAYER(SPECIES_ZUBAT) { Moves(MOVE_REST, MOVE_SLEEP_TALK, MOVE_AIR_CUTTER); } - 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_BUDEW) { Moves(MOVE_WORRY_SEED, MOVE_SLUDGE_BOMB); } + AI_FLAGS(aiFlags); + PLAYER(SPECIES_ZIGZAGOON) { Moves(move, MOVE_SLEEP_TALK, MOVE_HEADBUTT); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_WORRY_SEED, MOVE_HEADBUTT); } } WHEN { - TURN { MOVE(player, MOVE_AIR_CUTTER); EXPECT_MOVE(opponent, MOVE_WORRY_SEED); } + if (move == MOVE_REST) + TURN { MOVE(player, MOVE_HEADBUTT); EXPECT_MOVE(opponent, MOVE_WORRY_SEED); } + else + TURN { MOVE(player, MOVE_HEADBUTT); NOT_EXPECT_MOVE(opponent, MOVE_WORRY_SEED); } } } @@ -309,4 +319,23 @@ AI_SINGLE_BATTLE_TEST("AI uses Simple Beam against Contrary Leaf Storm") } } +AI_SINGLE_BATTLE_TEST("AI uses Skill Swap against Poison Heal") +{ + u8 status; + u64 aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT; + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_POISON; aiFlags |= AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; aiFlags |= AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; } + GIVEN { + PLAYER(SPECIES_SHROOMISH) { Ability(ABILITY_POISON_HEAL); Status1(status); } + AI_FLAGS(aiFlags); + OPPONENT(SPECIES_SPINDA) { Moves(MOVE_SKILL_SWAP, MOVE_HEADBUTT); } + } WHEN { + if (status == STATUS1_TOXIC_POISON) + TURN { EXPECT_MOVE(opponent, MOVE_SKILL_SWAP); } + else + TURN { NOT_EXPECT_MOVE(opponent, MOVE_SKILL_SWAP); } + } +} diff --git a/test/battle/ai/ai_doubles.c b/test/battle/ai/ai_doubles.c index cbf7c3423b..33c3eca074 100644 --- a/test/battle/ai/ai_doubles.c +++ b/test/battle/ai/ai_doubles.c @@ -348,7 +348,7 @@ AI_DOUBLE_BATTLE_TEST("AI sees corresponding absorbing abilities on partners") } } -AI_DOUBLE_BATTLE_TEST("AI knows if redirection abilities provide immunity to allies") +AI_DOUBLE_BATTLE_TEST("AI treats an ally's redirection ability appropriately (gen 4)") { KNOWN_FAILING; ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); @@ -356,26 +356,44 @@ AI_DOUBLE_BATTLE_TEST("AI knows if redirection abilities provide immunity to all ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY); ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); - u32 ability, move, species, config; + u32 ability, move, species; - PARAMETRIZE { species = SPECIES_SEAKING; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; config = GEN_4; } - PARAMETRIZE { species = SPECIES_SEAKING; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; config = GEN_5; } - PARAMETRIZE { species = SPECIES_SHELLOS; ability = ABILITY_STORM_DRAIN; move = MOVE_SURF; config = GEN_4; } - PARAMETRIZE { species = SPECIES_SHELLOS; ability = ABILITY_STORM_DRAIN; move = MOVE_SURF; config = GEN_5; } + PARAMETRIZE { species = SPECIES_SEAKING; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; } + PARAMETRIZE { species = SPECIES_SHELLOS; ability = ABILITY_STORM_DRAIN; move = MOVE_SURF; } GIVEN { - ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE); - WITH_CONFIG(B_REDIRECT_ABILITY_IMMUNITY, config); - PLAYER(SPECIES_ZIGZAGOON); - PLAYER(SPECIES_ZIGZAGOON); - OPPONENT(SPECIES_SLAKING) { Moves(move, MOVE_SCRATCH); } + WITH_CONFIG(B_REDIRECT_ABILITY_IMMUNITY, GEN_4); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(move, MOVE_HEADBUTT); } OPPONENT(species) { HP(1); Ability(ability); Moves(MOVE_ROUND); } } WHEN { - if (config == GEN_5) - TURN { EXPECT_MOVE(opponentLeft, move); } - else - TURN { EXPECT_MOVE(opponentLeft, MOVE_SCRATCH); } + TURN { EXPECT_MOVE(opponentLeft, MOVE_HEADBUTT); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI treats an ally's redirection ability appropriately (gen 5+)") +{ + ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); + ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); + + u32 ability, move, species; + + PARAMETRIZE { species = SPECIES_SEAKING; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; } + PARAMETRIZE { species = SPECIES_SHELLOS; ability = ABILITY_STORM_DRAIN; move = MOVE_SURF; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE); + WITH_CONFIG(B_REDIRECT_ABILITY_IMMUNITY, GEN_5); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(move, MOVE_HEADBUTT); } + OPPONENT(species) { HP(1); Ability(ability); Moves(MOVE_ROUND); } + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, move); } } } @@ -410,6 +428,41 @@ AI_DOUBLE_BATTLE_TEST("AI prioritizes Skill Swapping Contrary to allied mons tha } } +// Sandstorm is omitted on purpose. +// Tornadus is currently not willing to set up Sandstorm for its ally, but the actual purpose of this test is to demonstrate that Tornadus or Whimsicott will perform standard VGC openers. +// Rain Dance, Sunny Day, and Snowscape are the actually important ones; setting up a good Sandstorm test + functionality is less important and will be done in later PRs. +AI_DOUBLE_BATTLE_TEST("AI will set up weather for its ally") +{ + u32 goodWeather, badWeather, weatherTrigger; + u64 aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT; + + PARAMETRIZE { goodWeather = MOVE_SUNNY_DAY; badWeather = MOVE_RAIN_DANCE; weatherTrigger = MOVE_SOLAR_BEAM; } + PARAMETRIZE { goodWeather = MOVE_RAIN_DANCE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_THUNDER; } + PARAMETRIZE { goodWeather = MOVE_HAIL; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } + PARAMETRIZE { goodWeather = MOVE_SNOWSCAPE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } +// PARAMETRIZE { goodWeather = MOVE_SANDSTORM; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_SHORE_UP; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + goodWeather = MOVE_SUNNY_DAY; badWeather = MOVE_RAIN_DANCE; weatherTrigger = MOVE_SOLAR_BEAM; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + goodWeather = MOVE_RAIN_DANCE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_THUNDER; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + goodWeather = MOVE_HAIL; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + goodWeather = MOVE_SNOWSCAPE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } +// PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; +// goodWeather = MOVE_SANDSTORM; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_SHORE_UP; } + + GIVEN { + AI_FLAGS(aiFlags); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TORNADUS) { Ability(ABILITY_PRANKSTER); Moves(goodWeather, badWeather, MOVE_RETURN, MOVE_TAUNT); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(weatherTrigger, MOVE_EARTH_POWER); } + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, goodWeather); } + } +} + AI_DOUBLE_BATTLE_TEST("AI uses After You to set up Trick Room") { u32 move; @@ -433,6 +486,7 @@ AI_DOUBLE_BATTLE_TEST("AI uses After You to set up Trick Room") } } + AI_DOUBLE_BATTLE_TEST("AI uses Guard Split to improve its stats") {