From 11a7ea99f187d3df889b7d5db27f89e142a75712 Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Sat, 22 Mar 2025 17:23:13 -0400 Subject: [PATCH 1/4] fix choice switches --- src/battle_ai_switch_items.c | 3 ++- test/battle/ai/ai_choice.c | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index bd4dd88fd3..c7696a38cf 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -987,10 +987,11 @@ static bool32 ShouldSwitchIfEncored(u32 battler) static bool32 ShouldSwitchIfBadChoiceLock(u32 battler) { u32 holdEffect = GetBattlerHoldEffect(battler, FALSE); + u32 lastUsedMove = AI_DATA->lastUsedMove[battler]; if (HOLD_EFFECT_CHOICE(holdEffect) && IsBattlerItemEnabled(battler)) { - if (GetMoveCategory(AI_DATA->lastUsedMove[battler]) == DAMAGE_CATEGORY_STATUS && RandomPercentage(RNG_AI_SWITCH_CHOICE_LOCKED, GetSwitchChance(SHOULD_SWITCH_CHOICE_LOCKED))) + if ((GetMoveCategory(lastUsedMove) == DAMAGE_CATEGORY_STATUS || AI_GetMoveEffectiveness(lastUsedMove, battler, GetOppositeBattler(battler)) > UQ_4_12(0.0)) && RandomPercentage(RNG_AI_SWITCH_CHOICE_LOCKED, GetSwitchChance(SHOULD_SWITCH_CHOICE_LOCKED))) return SetSwitchinAndSwitch(battler, PARTY_SIZE); } diff --git a/test/battle/ai/ai_choice.c b/test/battle/ai/ai_choice.c index b19af6a073..842ed2de86 100644 --- a/test/battle/ai/ai_choice.c +++ b/test/battle/ai/ai_choice.c @@ -195,3 +195,19 @@ AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use status move if they are trappe } } } + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon will switch if locked into a move the player is immune to") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); + ASSUME(GetMoveType(MOVE_BODY_SLAM) == TYPE_NORMAL); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_GASTLY) { Level(1); Moves(MOVE_CELEBRATE); } + PLAYER(SPECIES_VAPOREON) { Ability(ABILITY_WATER_ABSORB); Moves(MOVE_SURF); } + OPPONENT(SPECIES_ZIGZAGOON) { Item(ITEM_CHOICE_BAND); Moves(MOVE_SURF, MOVE_BODY_SLAM); } + OPPONENT(SPECIES_ZIGZAGOON) { Item(ITEM_CHOICE_BAND); Moves(MOVE_SURF, MOVE_BODY_SLAM); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_SURF); SEND_OUT(player, 1); } + TURN { MOVE(player, MOVE_SURF); EXPECT_SWITCH(opponent, 1); } + } +} From 6a08b8fdec5351506ba0e87989ac2207209fe175 Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Sat, 22 Mar 2025 17:33:43 -0400 Subject: [PATCH 2/4] add gastly type assumption --- test/battle/ai/ai_choice.c | 1 + 1 file changed, 1 insertion(+) diff --git a/test/battle/ai/ai_choice.c b/test/battle/ai/ai_choice.c index 842ed2de86..590ad2c27f 100644 --- a/test/battle/ai/ai_choice.c +++ b/test/battle/ai/ai_choice.c @@ -199,6 +199,7 @@ AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use status move if they are trappe AI_SINGLE_BATTLE_TEST("Choiced Pokémon will switch if locked into a move the player is immune to") { GIVEN { + ASSUME(gSpeciesInfo[SPECIES_GASTLY].types[0] == TYPE_GHOST); ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); ASSUME(GetMoveType(MOVE_BODY_SLAM) == TYPE_NORMAL); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); From 50b0c5a2e32cb428ed8fa31f8234c07f369c2278 Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Sat, 22 Mar 2025 20:29:03 -0400 Subject: [PATCH 3/4] WHOOPS --- src/battle_ai_switch_items.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index c7696a38cf..9a7c2b9328 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -991,7 +991,7 @@ static bool32 ShouldSwitchIfBadChoiceLock(u32 battler) if (HOLD_EFFECT_CHOICE(holdEffect) && IsBattlerItemEnabled(battler)) { - if ((GetMoveCategory(lastUsedMove) == DAMAGE_CATEGORY_STATUS || AI_GetMoveEffectiveness(lastUsedMove, battler, GetOppositeBattler(battler)) > UQ_4_12(0.0)) && RandomPercentage(RNG_AI_SWITCH_CHOICE_LOCKED, GetSwitchChance(SHOULD_SWITCH_CHOICE_LOCKED))) + if ((GetMoveCategory(lastUsedMove) == DAMAGE_CATEGORY_STATUS || (AI_GetMoveEffectiveness(lastUsedMove, battler, GetOppositeBattler(battler)) == UQ_4_12(0.0))) && RandomPercentage(RNG_AI_SWITCH_CHOICE_LOCKED, GetSwitchChance(SHOULD_SWITCH_CHOICE_LOCKED))) return SetSwitchinAndSwitch(battler, PARTY_SIZE); } From ddbdc1f716f11a6b7865bed2980f3ff9825aadde Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Sat, 29 Mar 2025 13:54:44 -0400 Subject: [PATCH 4/4] Fixes across the board --- src/battle_ai_switch_items.c | 23 ++++++++++++++++++----- test/battle/ai/ai_choice.c | 20 +++++++++++++++++++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 9a7c2b9328..e6e808d035 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -164,6 +164,14 @@ static inline bool32 SetSwitchinAndSwitch(u32 battler, u32 switchinId) return TRUE; } +static bool32 AI_DoesChoiceItemBlockMove(u32 battler, u32 move) +{ + // Choice locked into something else + if (AI_DATA->lastUsedMove[battler] != MOVE_NONE && AI_DATA->lastUsedMove[battler] != move && HOLD_EFFECT_CHOICE(GetBattlerHoldEffect(battler, FALSE)) && IsBattlerItemEnabled(battler)) + return TRUE; + return FALSE; +} + // Note that as many return statements as possible are INTENTIONALLY put after all of the loops; // the function can take a max of about 0.06s to run, and this prevents the player from identifying // whether the mon will switch or not by seeing how long the delay is before they select a move @@ -194,7 +202,6 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) defType1 = gBattleMons[battler].types[0]; defType2 = gBattleMons[battler].types[1]; - // Check AI moves for damage dealt for (i = 0; i < MAX_MON_MOVES; i++) { aiMove = gBattleMons[battler].moves[i]; @@ -216,17 +223,16 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) if (!IsBattleMoveStatus(aiMove)) { // Check if mon has a super effective move - if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) >= UQ_4_12(2.0)) + if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) >= UQ_4_12(2.0) && !AI_DoesChoiceItemBlockMove(battler, aiMove)) hasSuperEffectiveMove = TRUE; // Get maximum damage mon can deal damageDealt = AI_GetDamage(battler, opposingBattler, i, AI_ATTACKING, AI_DATA); - if(damageDealt > maxDamageDealt) + if(damageDealt > maxDamageDealt && !AI_DoesChoiceItemBlockMove(battler, aiMove)) { maxDamageDealt = damageDealt; aiBestMove = aiMove; } - } } } @@ -988,10 +994,17 @@ static bool32 ShouldSwitchIfBadChoiceLock(u32 battler) { u32 holdEffect = GetBattlerHoldEffect(battler, FALSE); u32 lastUsedMove = AI_DATA->lastUsedMove[battler]; + u32 opposingBattler = GetOppositeBattler(battler); + bool32 moveAffectsTarget = TRUE; + + if (lastUsedMove != MOVE_NONE && (AI_GetMoveEffectiveness(lastUsedMove, battler, opposingBattler) == UQ_4_12(0.0) + || CanAbilityAbsorbMove(battler, opposingBattler, AI_DATA->abilities[opposingBattler], lastUsedMove, GetMoveType(lastUsedMove), ABILITY_CHECK_TRIGGER) + || CanAbilityBlockMove(battler, opposingBattler, lastUsedMove, AI_DATA->abilities[opposingBattler], ABILITY_CHECK_TRIGGER))) + moveAffectsTarget = FALSE; if (HOLD_EFFECT_CHOICE(holdEffect) && IsBattlerItemEnabled(battler)) { - if ((GetMoveCategory(lastUsedMove) == DAMAGE_CATEGORY_STATUS || (AI_GetMoveEffectiveness(lastUsedMove, battler, GetOppositeBattler(battler)) == UQ_4_12(0.0))) && RandomPercentage(RNG_AI_SWITCH_CHOICE_LOCKED, GetSwitchChance(SHOULD_SWITCH_CHOICE_LOCKED))) + if ((GetMoveCategory(lastUsedMove) == DAMAGE_CATEGORY_STATUS || !moveAffectsTarget) && RandomPercentage(RNG_AI_SWITCH_CHOICE_LOCKED, GetSwitchChance(SHOULD_SWITCH_CHOICE_LOCKED))) return SetSwitchinAndSwitch(battler, PARTY_SIZE); } diff --git a/test/battle/ai/ai_choice.c b/test/battle/ai/ai_choice.c index 590ad2c27f..6143d962ff 100644 --- a/test/battle/ai/ai_choice.c +++ b/test/battle/ai/ai_choice.c @@ -202,7 +202,7 @@ AI_SINGLE_BATTLE_TEST("Choiced Pokémon will switch if locked into a move the pl ASSUME(gSpeciesInfo[SPECIES_GASTLY].types[0] == TYPE_GHOST); ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); ASSUME(GetMoveType(MOVE_BODY_SLAM) == TYPE_NORMAL); - AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); PLAYER(SPECIES_GASTLY) { Level(1); Moves(MOVE_CELEBRATE); } PLAYER(SPECIES_VAPOREON) { Ability(ABILITY_WATER_ABSORB); Moves(MOVE_SURF); } OPPONENT(SPECIES_ZIGZAGOON) { Item(ITEM_CHOICE_BAND); Moves(MOVE_SURF, MOVE_BODY_SLAM); } @@ -212,3 +212,21 @@ AI_SINGLE_BATTLE_TEST("Choiced Pokémon will switch if locked into a move the pl TURN { MOVE(player, MOVE_SURF); EXPECT_SWITCH(opponent, 1); } } } + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon will only see choiced moves when considering switching with ShouldSwitchIfHasBadOdds") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_HASBADODDS_PERCENTAGE, 100, RNG_AI_SWITCH_HASBADODDS); + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_GASTLY].types[0] == TYPE_GHOST); + ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); + ASSUME(GetMoveType(MOVE_BODY_SLAM) == TYPE_NORMAL); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_GASTLY) { Level(1); Moves(MOVE_CELEBRATE); } + PLAYER(SPECIES_ZIGZAGOON) { Item(ITEM_CHOICE_BAND); Moves(MOVE_CLOSE_COMBAT); } + OPPONENT(SPECIES_ZIGZAGOON) { Item(ITEM_CHOICE_BAND); Moves(MOVE_SURF, MOVE_CLOSE_COMBAT); } + OPPONENT(SPECIES_BRONZONG) { Moves(MOVE_CLOSE_COMBAT); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_SURF); SEND_OUT(player, 1); } + TURN { MOVE(player, MOVE_CLOSE_COMBAT); EXPECT_SWITCH(opponent, 1); } + } +}