From b109c4c36bbd7818c66e73b3be498f15ccbba49c Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Tue, 25 Feb 2025 07:06:39 -0500 Subject: [PATCH] Fix Substitute / Shed Tail Switch AI (#6334) --- include/battle_ai_util.h | 3 ++- src/battle_ai_main.c | 3 ++- src/battle_ai_switch_items.c | 31 +++++++++++++++++++++------ src/battle_ai_util.c | 33 +++++++++++++++++++++-------- test/battle/move_effect/shed_tail.c | 14 ++++++------ 5 files changed, 60 insertions(+), 24 deletions(-) diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index a0898c7ef7..ba6e7cd69c 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -155,6 +155,7 @@ bool32 HasHighCritRatioMove(u32 battler); bool32 HasMagicCoatAffectedMove(u32 battler); bool32 HasSnatchAffectedMove(u32 battler); bool32 IsHazardClearingMove(u32 move); +bool32 IsSubstituteEffect(u32 effect); // status checks bool32 AI_CanGetFrostbite(u32 battler, u32 ability); @@ -216,7 +217,7 @@ bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef); bool32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData); void IncreaseTidyUpScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); bool32 AI_ShouldSpicyExtract(u32 battlerAtk, u32 battlerAtkPartner, u32 move, struct AiLogicData *aiData); -void IncreaseSubstituteMoveScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); +u32 IncreaseSubstituteMoveScore(u32 battlerAtk, u32 battlerDef, u32 move); bool32 IsBattlerPredictedToSwitch(u32 battler); bool32 HasLowAccuracyMove(u32 battlerAtk, u32 battlerDef); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 09cdd3a9f3..e3d0dafbe5 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -3650,7 +3650,8 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) break; case EFFECT_SUBSTITUTE: case EFFECT_SHED_TAIL: - IncreaseSubstituteMoveScore(battlerAtk, battlerDef, move, &score); + ADJUST_SCORE(IncreaseSubstituteMoveScore(battlerAtk, battlerDef, move)); + break; case EFFECT_MIMIC: if (AI_IsFaster(battlerAtk, battlerDef, move)) { diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 93adc01066..663e9fb633 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -1021,6 +1021,23 @@ static bool32 ShouldSwitchIfAttackingStatsLowered(u32 battler) return FALSE; } +static bool32 HasGoodSubstituteMove(u32 battler) +{ + int i; + u32 aiMove, aiMoveEffect, opposingBattler = GetOppositeBattler(battler); + for (i = 0; i < MAX_MON_MOVES; i++) + { + aiMove = gBattleMons[battler].moves[i]; + aiMoveEffect = GetMoveEffect(aiMove); + if (IsSubstituteEffect(aiMoveEffect)) + { + if (IncreaseSubstituteMoveScore(battler, opposingBattler, aiMove) > 0) + return TRUE; + } + } + return FALSE; +} + bool32 ShouldSwitch(u32 battler) { u32 battlerIn1, battlerIn2; @@ -1085,19 +1102,21 @@ bool32 ShouldSwitch(u32 battler) return FALSE; // NOTE: The sequence of the below functions matter! Do not change unless you have carefully considered the outcome. - // Since the order is sequencial, and some of these functions prompt switch to specific party members. + // Since the order is sequential, and some of these functions prompt switch to specific party members. + + // FindMon functions can prompt a switch to specific party members that override GetMostSuitableMonToSwitchInto + // The rest can prompt a switch to party member returned by GetMostSuitableMonToSwitchInto - // These Functions can prompt switch to specific party members that override GetMostSuitableMonToSwitchInto if (FindMonThatHitsWonderGuard(battler)) return TRUE; - if (FindMonThatAbsorbsOpponentsMove(battler)) - return TRUE; - - // These Functions can prompt switch to party member returned by GetMostSuitableMonToSwitchInto if ((AI_THINKING_STRUCT->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING) && (CanMonSurviveHazardSwitchin(battler) == FALSE)) return FALSE; + if (HasGoodSubstituteMove(battler)) + return FALSE; if (ShouldSwitchIfTrapperInParty(battler)) return TRUE; + if (FindMonThatAbsorbsOpponentsMove(battler)) + return TRUE; if (ShouldSwitchIfOpponentChargingOrInvulnerable(battler)) return TRUE; if (ShouldSwitchIfTruant(battler)) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 7b45c539e5..57fcc0b7ac 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -2427,6 +2427,19 @@ bool32 IsSwitchOutEffect(u32 effect) } } +bool32 IsSubstituteEffect(u32 effect) +{ + // Substitute effects like Substitute, Shed Tail, etc. + switch (effect) + { + case EFFECT_SUBSTITUTE: + case EFFECT_SHED_TAIL: + return TRUE; + default: + return FALSE; + } +} + bool32 IsChaseEffect(u32 effect) { // Effects that hit switching out mons like Pursuit @@ -4232,31 +4245,32 @@ bool32 AI_ShouldSpicyExtract(u32 battlerAtk, u32 battlerAtkPartner, u32 move, st && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL)); } -void IncreaseSubstituteMoveScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) +u32 IncreaseSubstituteMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) { u32 effect = GetMoveEffect(move); + u32 scoreIncrease = 0; if (effect == EFFECT_SUBSTITUTE) // Substitute specific { if (HasAnyKnownMove(battlerDef) && GetBestDmgFromBattler(battlerDef, battlerAtk) < gBattleMons[battlerAtk].maxHP / 4) - ADJUST_SCORE_PTR(GOOD_EFFECT); + scoreIncrease += GOOD_EFFECT; } else if (effect == EFFECT_SHED_TAIL) // Shed Tail specific { if ((ShouldPivot(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], move, AI_THINKING_STRUCT->movesetIndex)) && (HasAnyKnownMove(battlerDef) && (GetBestDmgFromBattler(battlerDef, battlerAtk) < gBattleMons[battlerAtk].maxHP / 2))) - ADJUST_SCORE_PTR(BEST_EFFECT); + scoreIncrease += BEST_EFFECT; } if (gStatuses3[battlerDef] & STATUS3_PERISH_SONG) - ADJUST_SCORE_PTR(GOOD_EFFECT); + scoreIncrease += GOOD_EFFECT; if (gBattleMons[battlerDef].status1 & STATUS1_SLEEP) - ADJUST_SCORE_PTR(GOOD_EFFECT); + scoreIncrease += GOOD_EFFECT; else if (gBattleMons[battlerDef].status1 & (STATUS1_BURN | STATUS1_PSN_ANY | STATUS1_FROSTBITE)) - ADJUST_SCORE_PTR(DECENT_EFFECT); + scoreIncrease += DECENT_EFFECT; if (IsBattlerPredictedToSwitch(battlerDef)) - ADJUST_SCORE(DECENT_EFFECT); + scoreIncrease += DECENT_EFFECT; if (HasMoveEffect(battlerDef, EFFECT_SLEEP) || HasMoveEffect(battlerDef, EFFECT_TOXIC) @@ -4265,10 +4279,11 @@ void IncreaseSubstituteMoveScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 * || HasMoveEffect(battlerDef, EFFECT_WILL_O_WISP) || HasMoveEffect(battlerDef, EFFECT_CONFUSE) || HasMoveEffect(battlerDef, EFFECT_LEECH_SEED)) - ADJUST_SCORE_PTR(GOOD_EFFECT); + scoreIncrease += GOOD_EFFECT; if (AI_DATA->hpPercents[battlerAtk] > 70) - ADJUST_SCORE_PTR(WEAK_EFFECT); + scoreIncrease += WEAK_EFFECT; + return scoreIncrease; } bool32 HasLowAccuracyMove(u32 battlerAtk, u32 battlerDef) diff --git a/test/battle/move_effect/shed_tail.c b/test/battle/move_effect/shed_tail.c index 4667eab1ad..f4498c7e34 100644 --- a/test/battle/move_effect/shed_tail.c +++ b/test/battle/move_effect/shed_tail.c @@ -86,22 +86,22 @@ SINGLE_BATTLE_TEST("Shed Tail's HP cost doesn't trigger effects that trigger on } } -// Passes for some reason even though it seems there is some code missing -/* -AI_SINGLE_BATTLE_TEST("AI will use Shed Tail to pivot to another mon while in damage stalemate with player") +AI_SINGLE_BATTLE_TEST("AI will use Shed Tail to pivot to another mon while in damage stalemate with player rather than hard switching") { - KNOWN_FAILING; // missing AI code + u32 aiFlags; + PARAMETRIZE { aiFlags = 0; } + PARAMETRIZE { aiFlags = AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_MON_CHOICES; } GIVEN { - 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 | aiFlags); PLAYER(SPECIES_WOBBUFFET) { Speed(100); Ability(ABILITY_RUN_AWAY); Moves(MOVE_TACKLE, MOVE_CELEBRATE); } OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Ability(ABILITY_RUN_AWAY); Moves(MOVE_CONFUSION, MOVE_SHED_TAIL); } OPPONENT(SPECIES_SCIZOR) { Speed(101); Moves(MOVE_CELEBRATE, MOVE_X_SCISSOR); } } WHEN { - TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_CONFUSION); } + if (aiFlags == 0) + TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_CONFUSION); } TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_SHED_TAIL); } } } -*/ SINGLE_BATTLE_TEST("Shed Tail creates a Substitute with 1/4 of user maximum health") {