From 7fb5d98bf6d9a5f41eb416ebb2573f0191c9f24c Mon Sep 17 00:00:00 2001 From: surskitty Date: Tue, 12 Aug 2025 17:12:45 -0400 Subject: [PATCH] AI uses Tailwind. (#7515) --- include/battle_ai_util.h | 1 - src/battle_ai_field_statuses.c | 8 +++--- src/battle_ai_main.c | 42 +++++++++++++++++++++++++++-- src/battle_ai_util.c | 21 --------------- test/battle/ai/ai_check_viability.c | 18 +++++++++++++ test/battle/ai/ai_doubles.c | 28 +++++++++++++++++++ 6 files changed, 90 insertions(+), 28 deletions(-) diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 5759427f4b..2e62a1340b 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -125,7 +125,6 @@ bool32 BattlerWillFaintFromWeather(u32 battler, u32 ability); bool32 BattlerWillFaintFromSecondaryDamage(u32 battler, u32 ability); bool32 ShouldTryOHKO(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbility, u32 move); bool32 ShouldUseRecoilMove(u32 battlerAtk, u32 battlerDef, u32 recoilDmg, u32 moveIndex); -u32 GetBattlerSideSpeedAverage(u32 battler); bool32 ShouldAbsorb(u32 battlerAtk, u32 battlerDef, u32 move, s32 damage); bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent); bool32 ShouldSetScreen(u32 battlerAtk, u32 battlerDef, enum BattleMoveEffects moveEffect); diff --git a/src/battle_ai_field_statuses.c b/src/battle_ai_field_statuses.c index 423910a0e5..bd7bdd8e17 100644 --- a/src/battle_ai_field_statuses.c +++ b/src/battle_ai_field_statuses.c @@ -429,13 +429,13 @@ static enum FieldEffectOutcome BenefitsFromPsychicTerrain(u32 battler) static enum FieldEffectOutcome BenefitsFromTrickRoom(u32 battler) { // If we're in singles, we literally only care about speed. - if (!IsDoubleBattle()) + if (IsBattle1v1()) { - if (GetBattlerSideSpeedAverage(battler) < GetBattlerSideSpeedAverage(FOE(battler))) + if (gAiLogicData->speedStats[battler] < gAiLogicData->speedStats[FOE(battler)]) return FIELD_EFFECT_POSITIVE; // If we tie, we shouldn't change trick room state. - else if (GetBattlerSideSpeedAverage(battler) == GetBattlerSideSpeedAverage(FOE(battler))) - return FIELD_EFFECT_NEUTRAL; + else if (gAiLogicData->speedStats[battler] == gAiLogicData->speedStats[FOE(battler)]) + return FIELD_EFFECT_NEUTRAL; else return FIELD_EFFECT_NEGATIVE; } diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 78f1b72b28..30093aafd4 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -5092,6 +5092,9 @@ case EFFECT_GUARD_SPLIT: { if (!(gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && ShouldSetFieldStatus(battlerAtk, STATUS_FIELD_TRICK_ROOM)) ADJUST_SCORE(GOOD_EFFECT); + // Set it for next pokemon in singles. + else if (!(gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && !hasPartner && (CountUsablePartyMons(battlerAtk) != 0)) + ADJUST_SCORE(DECENT_EFFECT); // Don't unset it on last turn. else if (gFieldStatuses & STATUS_FIELD_TRICK_ROOM && gFieldTimers.trickRoomTimer != gBattleTurnCounter && ShouldClearFieldStatus(battlerAtk, STATUS_FIELD_TRICK_ROOM)) ADJUST_SCORE(GOOD_EFFECT); @@ -5200,9 +5203,44 @@ case EFFECT_GUARD_SPLIT: ADJUST_SCORE(DECENT_EFFECT); // Attacker partner wouldn't go before target break; case EFFECT_TAILWIND: - if (GetBattlerSideSpeedAverage(battlerAtk) < GetBattlerSideSpeedAverage(battlerDef)) - ADJUST_SCORE(GOOD_EFFECT); + { + if (gFieldStatuses & STATUS_FIELD_TRICK_ROOM && gFieldTimers.trickRoomTimer != gBattleTurnCounter) + break; + + if (HasBattlerSideMoveWithEffect(battlerAtk, EFFECT_ELECTRO_BALL)) + ADJUST_SCORE(WEAK_EFFECT); + + if (isBattle1v1) + { + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED); + + if (CountUsablePartyMons(battlerAtk) != 0) + ADJUST_SCORE(WEAK_EFFECT); + } + else + { + u32 tailwindScore = 0; + u32 speed = aiData->speedStats[battlerAtk]; + u32 partnerSpeed = aiData->speedStats[BATTLE_PARTNER(battlerAtk)]; + u32 foe1Speed = aiData->speedStats[FOE(battlerAtk)]; + u32 foe2Speed = aiData->speedStats[BATTLE_PARTNER(FOE(battlerAtk))]; + + if (speed <= foe1Speed && (speed * 2) > foe1Speed) + tailwindScore += 1; + if (speed <= foe2Speed && (speed * 2) > foe2Speed) + tailwindScore += 1; + if (partnerSpeed <= foe1Speed && (speed * 2) > foe1Speed) + tailwindScore += 1; + if (partnerSpeed <= foe1Speed && (speed * 2) > foe1Speed) + tailwindScore += 1; + + if (tailwindScore > 0) + tailwindScore += 1; + + ADJUST_SCORE(tailwindScore); + } break; + } case EFFECT_LUCKY_CHANT: if (isBattle1v1 && CountUsablePartyMons(battlerDef) > 0) ADJUST_SCORE(GOOD_EFFECT); diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index e6dec3e58d..a0e5e7716b 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -3687,27 +3687,6 @@ bool32 AnyPartyMemberStatused(u32 battlerId, bool32 checkSoundproof) return FALSE; } -u32 GetBattlerSideSpeedAverage(u32 battler) -{ - u32 speed1 = 0; - u32 speed2 = 0; - u32 numBattlersAlive = 0; - - if (IsBattlerAlive(battler)) - { - speed1 = gAiLogicData->speedStats[battler]; - numBattlersAlive++; - } - - if (HasPartner(battler)) - { - speed2 = gAiLogicData->speedStats[BATTLE_PARTNER(battler)]; - numBattlersAlive++; - } - - return (speed1 + speed2) / numBattlersAlive; -} - bool32 ShouldUseRecoilMove(u32 battlerAtk, u32 battlerDef, u32 recoilDmg, u32 moveIndex) { if (recoilDmg >= gBattleMons[battlerAtk].hp //Recoil kills attacker diff --git a/test/battle/ai/ai_check_viability.c b/test/battle/ai/ai_check_viability.c index f18f7aceb1..c586b6fc81 100644 --- a/test/battle/ai/ai_check_viability.c +++ b/test/battle/ai/ai_check_viability.c @@ -340,6 +340,24 @@ AI_SINGLE_BATTLE_TEST("AI uses Skill Swap against Poison Heal") } } +AI_SINGLE_BATTLE_TEST("AI uses Trick Room (singles)") +{ + u32 speed; + PARAMETRIZE { speed = 10; } + PARAMETRIZE { speed = 20; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Speed(11); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(speed); Moves(MOVE_TACKLE, MOVE_TRICK_ROOM); } + } WHEN { + if (speed == 10) + TURN { EXPECT_MOVE(opponent, MOVE_TRICK_ROOM); } + else + TURN { NOT_EXPECT_MOVE(opponent, MOVE_TRICK_ROOM); } + } +} + AI_SINGLE_BATTLE_TEST("AI uses Quick Guard against Quick Attack when opponent would take poison damage") { PASSES_RANDOMLY(PREDICT_MOVE_CHANCE, 100, RNG_AI_PREDICT_MOVE); diff --git a/test/battle/ai/ai_doubles.c b/test/battle/ai/ai_doubles.c index ab6b4a4425..9e6edc4c56 100644 --- a/test/battle/ai/ai_doubles.c +++ b/test/battle/ai/ai_doubles.c @@ -594,6 +594,34 @@ AI_DOUBLE_BATTLE_TEST("AI uses Trick Room intelligently") TURN { NOT_EXPECT_MOVE(opponentRight, MOVE_TRICK_ROOM); } } } + +AI_DOUBLE_BATTLE_TEST("AI uses Tailwind") +{ + u32 speed1, speed2, speed3, speed4; + + PARAMETRIZE { speed1 = 20; speed2 = 20; speed3 = 20; speed4 = 20; } + PARAMETRIZE { speed1 = 20; speed2 = 20; speed3 = 5; speed4 = 5; } + PARAMETRIZE { speed1 = 20; speed2 = 20; speed3 = 15; speed4 = 15; } + PARAMETRIZE { speed1 = 1; speed2 = 1; speed3 = 5; speed4 = 5; } + PARAMETRIZE { speed1 = 1; speed2 = 20; speed3 = 15; speed4 = 15; } + PARAMETRIZE { speed1 = 1; speed2 = 20; speed3 = 20; speed4 = 15; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_AFTER_YOU) == EFFECT_AFTER_YOU); + ASSUME(GetMoveEffect(MOVE_TRICK_ROOM) == EFFECT_TRICK_ROOM); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE); + PLAYER(SPECIES_WOBBUFFET) { Speed(speed1); } + PLAYER(SPECIES_WOBBUFFET) { Speed(speed2); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(speed3); Moves(MOVE_TAILWIND, MOVE_HEADBUTT); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(speed4); Moves(MOVE_TAILWIND, MOVE_HEADBUTT); } + } WHEN { + if (speed3 > 10) + TURN { EXPECT_MOVE(opponentLeft, MOVE_TAILWIND); } + else + TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_TAILWIND); } + } +} + AI_DOUBLE_BATTLE_TEST("AI uses Guard Split to improve its stats") {