From d218e024dfb0aec71821c449cc7ff31b78dc7093 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 3 Feb 2024 13:58:52 +0100 Subject: [PATCH 1/4] Adds Powerful status move flag --- include/battle_ai_main.h | 3 +- include/config.h | 2 +- include/constants/battle_ai.h | 4 +- src/battle_ai_main.c | 91 ++++++++++++++++++++++++++++---- src/battle_ai_util.c | 4 ++ test/battle/ai_powerful_status.c | 22 ++++++++ 6 files changed, 111 insertions(+), 15 deletions(-) create mode 100644 test/battle/ai_powerful_status.c diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index 8f482c581271..18148a89b04e 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -29,7 +29,8 @@ #define STAT_CHANGE_ACC 10 #define STAT_CHANGE_EVASION 11 -#define BEST_DAMAGE_MOVE 1 // Move with the most amount of hits with the best accuracy/effect +#define BEST_DAMAGE_MOVE 1 // Move with the most amount of hits with the best accuracy/effect +#define POWERFUL_STATUS_MOVE 10 // Moves with this score will be chosen over a move that faints target // Temporary scores that are added together to determine a final score at the at of AI_CalcMoveScore #define WEAK_EFFECT 1 diff --git a/include/config.h b/include/config.h index f4be812643dc..17dda42411dc 100644 --- a/include/config.h +++ b/include/config.h @@ -6,7 +6,7 @@ // still has them in the ROM. This is because the developers forgot // to define NDEBUG before release, however this has been changed as // Ruby's actual debug build does not use the AGBPrint features. -#define NDEBUG +// #define NDEBUG // To enable printf debugging, comment out "#define NDEBUG". This allows // the various AGBPrint functions to be used. (See include/gba/isagbprint.h). diff --git a/include/constants/battle_ai.h b/include/constants/battle_ai.h index ad489a4dd618..9007dcd8dd06 100644 --- a/include/constants/battle_ai.h +++ b/include/constants/battle_ai.h @@ -41,13 +41,13 @@ #define AI_FLAG_HELP_PARTNER (1 << 11) // AI can try to help partner. If not set, will tend not to target partner #define AI_FLAG_PREFER_STATUS_MOVES (1 << 12) // AI gets a score bonus for status moves. Should be combined with AI_FLAG_CHECK_BAD_MOVE to prevent using only status moves #define AI_FLAG_STALL (1 << 13) // AI stalls battle and prefers secondary damage/trapping/etc. TODO not finished -#define AI_FLAG_SCREENER (1 << 14) // AI prefers screening effects like reflect, mist, etc. TODO unfinished +#define AI_FLAG_POWERFUL_STATUS (1 << 14) // AI prefers moves that set up field effects or side statuses, even over a kill. Usega with caution #define AI_FLAG_SMART_SWITCHING (1 << 15) // AI includes a lot more switching checks #define AI_FLAG_ACE_POKEMON (1 << 16) // AI has an Ace Pokemon. The last Pokemon in the party will not be used until it's the last one remaining. #define AI_FLAG_OMNISCIENT (1 << 17) // AI has full knowledge of player moves, abilities, hold items #define AI_FLAG_SMART_MON_CHOICES (1 << 18) // AI will make smarter decisions when choosing which mon to send out mid-battle and after a KO, which are separate decisions. Pairs very well with AI_FLAG_SMART_SWITCHING. -#define AI_FLAG_COUNT 18 +#define AI_FLAG_COUNT 19 // 'other' ai logic flags #define AI_FLAG_ROAMING (1 << 29) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index a6d320b12926..b99ce7b70859 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -52,6 +52,7 @@ static s32 AI_Roaming(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); static s32 AI_Safari(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); static s32 AI_FirstBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); +static s32 AI_PowerfulStatus(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); static s32 (*const sBattleAiFuncTable[])(u32, u32, u32, s32) = @@ -65,10 +66,10 @@ static s32 (*const sBattleAiFuncTable[])(u32, u32, u32, s32) = [6] = AI_PreferBatonPass, // AI_FLAG_PREFER_BATON_PASS [7] = AI_DoubleBattle, // AI_FLAG_DOUBLE_BATTLE [8] = AI_HPAware, // AI_FLAG_HP_AWARE - [9] = NULL, // AI_FLAG_NEGATE_UNAWARE - [10] = NULL, // AI_FLAG_WILL_SUICIDE - [11] = NULL, // AI_FLAG_HELP_PARTNER - [12] = NULL, // Unused + [9] = AI_PowerfulStatus, // AI_FLAG_POWERFUL_STATUS + [10] = NULL, // AI_FLAG_NEGATE_UNAWARE + [11] = NULL, // AI_FLAG_WILL_SUICIDE + [12] = NULL, // AI_FLAG_HELP_PARTNER [13] = NULL, // Unused [14] = NULL, // Unused [15] = NULL, // Unused @@ -3481,8 +3482,6 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(BEST_EFFECT); if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_LIGHT_CLAY) ADJUST_SCORE(DECENT_EFFECT); - if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_SCREENER) - ADJUST_SCORE(DECENT_EFFECT); } break; case EFFECT_REST: @@ -3510,10 +3509,6 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) if (ShouldTrap(battlerAtk, battlerDef, move)) ADJUST_SCORE(GOOD_EFFECT); break; - case EFFECT_MIST: - if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_SCREENER) - ADJUST_SCORE(DECENT_EFFECT); - break; case EFFECT_FOCUS_ENERGY: case EFFECT_LASER_FOCUS: if (aiData->abilities[battlerAtk] == ABILITY_SUPER_LUCK @@ -4675,7 +4670,6 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score // Calculates score based on effects of a move score += AI_CalcMoveScore(battlerAtk, battlerDef, move); - return score; } @@ -5116,6 +5110,81 @@ static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) return score; } +static s32 AI_PowerfulStatus(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +{ + u32 moveEffect = gMovesInfo[move].effect; + + DebugPrintf("works"); + + if (IsDoubleBattle() || gMovesInfo[move].category != DAMAGE_CATEGORY_STATUS || gMovesInfo[AI_DATA->partnerMove].effect == moveEffect) + return score; + + switch (moveEffect) + { + case EFFECT_TAILWIND: + if (!(gSideTimers[GetBattlerSide(battlerAtk)].tailwindTimer || (gFieldStatuses & STATUS_FIELD_TRICK_ROOM && gFieldTimers.trickRoomTimer > 1))) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_TRICK_ROOM: + // if (!((gFieldStatuses & STATUS_FIELD_TRICK_ROOM) || HasMoveEffect(battlerDef, EFFECT_TRICK_ROOM))) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_MAGIC_ROOM: + if (!((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || HasMoveEffect(battlerDef, EFFECT_MAGIC_ROOM))) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_WONDER_ROOM: + if (!((gFieldStatuses & STATUS_FIELD_WONDER_ROOM) || HasMoveEffect(battlerDef, EFFECT_WONDER_ROOM))) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_GRAVITY: + if (!(gFieldStatuses & STATUS_FIELD_GRAVITY)) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_SAFEGUARD: + if (!(gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_SAFEGUARD)) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_MIST: + if (!(gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_MIST)) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_LIGHT_SCREEN: + case EFFECT_REFLECT: + case EFFECT_AURORA_VEIL: + if (ShouldSetScreen(battlerAtk, battlerDef, moveEffect)) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_SPIKES: + case EFFECT_STEALTH_ROCK: + case EFFECT_STICKY_WEB: + case EFFECT_TOXIC_SPIKES: + if (!(AI_DATA->abilities[battlerDef] == ABILITY_MAGIC_BOUNCE || CountUsablePartyMons(battlerDef) == 0 || HasMoveWithMoveEffect(battlerDef, MOVE_EFFECT_RAPIDSPIN))) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_GRASSY_TERRAIN: + if (!(gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN)) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_ELECTRIC_TERRAIN: + if (!(gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN)) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_PSYCHIC_TERRAIN: + if (!(gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN)) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_MISTY_TERRAIN: + if (!(gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN)) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_SANDSTORM: + if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_SANDSTORM | B_WEATHER_PRIMAL_ANY))) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_SUNNY_DAY: + if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_SUN | B_WEATHER_PRIMAL_ANY))) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_RAIN_DANCE: + if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_RAIN | B_WEATHER_PRIMAL_ANY))) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_HAIL: + if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_HAIL | B_WEATHER_PRIMAL_ANY))) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + case EFFECT_SNOWSCAPE: + if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_SNOW | B_WEATHER_PRIMAL_ANY))) + RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + default: + return FALSE; + } +} + static void AI_Flee(void) { AI_THINKING_STRUCT->aiAction |= (AI_ACTION_DONE | AI_ACTION_FLEE | AI_ACTION_DO_NOT_ATTACK); diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 4d3e69485d2a..b1dd76ce5715 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -2863,6 +2863,10 @@ bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent) bool32 ShouldSetScreen(u32 battlerAtk, u32 battlerDef, u32 moveEffect) { u32 atkSide = GetBattlerSide(battlerAtk); + + if (HasMoveEffect(battlerDef, EFFECT_BRICK_BREAK)) // Don't waste a turn if screens will be broken + return FALSE; + switch (moveEffect) { case EFFECT_AURORA_VEIL: diff --git a/test/battle/ai_powerful_status.c b/test/battle/ai_powerful_status.c new file mode 100644 index 000000000000..b66f75439394 --- /dev/null +++ b/test/battle/ai_powerful_status.c @@ -0,0 +1,22 @@ +#include "global.h" +#include "test/battle.h" +#include "battle_ai_util.h" + +AI_SINGLE_BATTLE_TEST("AI prefers to set up a powerful Status over fainting a target") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TRICK_ROOM].effect == EFFECT_TRICK_ROOM); + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(gMovesInfo[MOVE_TACKLE].power > 0); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_POWERFUL_STATUS); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_DUSCLOPS) { Moves(MOVE_TRICK_ROOM, MOVE_TACKLE); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_TRICK_ROOM); } + TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); } + } + SCENE { + MESSAGE("Wynaut fainted!"); + } +} From b68cdb9afafdb5b4c41cf243b44e4a07a530f1e2 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 10 Feb 2024 12:10:01 +0100 Subject: [PATCH 2/4] fix flag --- include/battle_ai_main.h | 1 + include/constants/battle_ai.h | 12 ++--- src/battle_ai_main.c | 92 +++++++++++++++++++------------- src/data/trainers.h | 2 +- test/battle/ai_powerful_status.c | 26 +++++++-- 5 files changed, 87 insertions(+), 46 deletions(-) diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index 18148a89b04e..168d26b0b0ce 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -76,6 +76,7 @@ #define RETURN_SCORE_PLUS(val) \ { \ ADJUST_SCORE(val); \ + DebugPrintf("score: %d", score); \ return score; \ } diff --git a/include/constants/battle_ai.h b/include/constants/battle_ai.h index 9007dcd8dd06..979ca80be43c 100644 --- a/include/constants/battle_ai.h +++ b/include/constants/battle_ai.h @@ -34,14 +34,14 @@ #define AI_FLAG_PREFER_BATON_PASS (1 << 6) #define AI_FLAG_DOUBLE_BATTLE (1 << 7) // removed, split between AI_FLAG_CHECK_BAD_MOVE & AI_FLAG_CHECK_GOOD_MOVE #define AI_FLAG_HP_AWARE (1 << 8) +#define AI_FLAG_POWERFUL_STATUS (1 << 9) // AI prefers moves that set up field effects or side statuses, even over a kill. Usega with caution // New, Trainer Handicap Flags -#define AI_FLAG_NEGATE_UNAWARE (1 << 9) // AI is NOT aware of negating effects like wonder room, mold breaker, etc -#define AI_FLAG_WILL_SUICIDE (1 << 10) // AI will use explosion / self destruct / final gambit / etc +#define AI_FLAG_NEGATE_UNAWARE (1 << 10) // AI is NOT aware of negating effects like wonder room, mold breaker, etc +#define AI_FLAG_WILL_SUICIDE (1 << 11) // AI will use explosion / self destruct / final gambit / etc // New, Trainer Strategy Flags -#define AI_FLAG_HELP_PARTNER (1 << 11) // AI can try to help partner. If not set, will tend not to target partner -#define AI_FLAG_PREFER_STATUS_MOVES (1 << 12) // AI gets a score bonus for status moves. Should be combined with AI_FLAG_CHECK_BAD_MOVE to prevent using only status moves -#define AI_FLAG_STALL (1 << 13) // AI stalls battle and prefers secondary damage/trapping/etc. TODO not finished -#define AI_FLAG_POWERFUL_STATUS (1 << 14) // AI prefers moves that set up field effects or side statuses, even over a kill. Usega with caution +#define AI_FLAG_HELP_PARTNER (1 << 12) // AI can try to help partner. If not set, will tend not to target partner +#define AI_FLAG_PREFER_STATUS_MOVES (1 << 13) // AI gets a score bonus for status moves. Should be combined with AI_FLAG_CHECK_BAD_MOVE to prevent using only status moves +#define AI_FLAG_STALL (1 << 14) // AI stalls battle and prefers secondary damage/trapping/etc. TODO not finished #define AI_FLAG_SMART_SWITCHING (1 << 15) // AI includes a lot more switching checks #define AI_FLAG_ACE_POKEMON (1 << 16) // AI has an Ace Pokemon. The last Pokemon in the party will not be used until it's the last one remaining. #define AI_FLAG_OMNISCIENT (1 << 17) // AI has full knowledge of player moves, abilities, hold items diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index b99ce7b70859..50c0ea08651b 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -2341,15 +2341,18 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { ADJUST_SCORE(-10); } - else if (gFieldStatuses & STATUS_FIELD_TRICK_ROOM) // Trick Room Up + else if (!(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_POWERFUL_STATUS)) { - if (GetBattlerSideSpeedAverage(battlerAtk) < GetBattlerSideSpeedAverage(battlerDef)) // Attacker side slower than target side - ADJUST_SCORE(-10); // Keep the Trick Room up - } - else - { - if (GetBattlerSideSpeedAverage(battlerAtk) >= GetBattlerSideSpeedAverage(battlerDef)) // Attacker side faster than target side - ADJUST_SCORE(-10); // Keep the Trick Room down + if (gFieldStatuses & STATUS_FIELD_TRICK_ROOM) // Trick Room Up + { + if (GetBattlerSideSpeedAverage(battlerAtk) < GetBattlerSideSpeedAverage(battlerDef)) // Attacker side slower than target side + ADJUST_SCORE(-10); // Keep the Trick Room up + } + else + { + if (GetBattlerSideSpeedAverage(battlerAtk) >= GetBattlerSideSpeedAverage(battlerDef)) // Attacker side faster than target side + ADJUST_SCORE(-10); // Keep the Trick Room down + } } break; case EFFECT_MAGIC_ROOM: @@ -5114,75 +5117,92 @@ static s32 AI_PowerfulStatus(u32 battlerAtk, u32 battlerDef, u32 move, s32 score { u32 moveEffect = gMovesInfo[move].effect; - DebugPrintf("works"); - - if (IsDoubleBattle() || gMovesInfo[move].category != DAMAGE_CATEGORY_STATUS || gMovesInfo[AI_DATA->partnerMove].effect == moveEffect) + if (gMovesInfo[move].category != DAMAGE_CATEGORY_STATUS || gMovesInfo[AI_DATA->partnerMove].effect == moveEffect) return score; switch (moveEffect) { case EFFECT_TAILWIND: - if (!(gSideTimers[GetBattlerSide(battlerAtk)].tailwindTimer || (gFieldStatuses & STATUS_FIELD_TRICK_ROOM && gFieldTimers.trickRoomTimer > 1))) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + if (!gSideTimers[GetBattlerSide(battlerAtk)].tailwindTimer && !(gFieldStatuses & STATUS_FIELD_TRICK_ROOM && gFieldTimers.trickRoomTimer > 1)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_TRICK_ROOM: - // if (!((gFieldStatuses & STATUS_FIELD_TRICK_ROOM) || HasMoveEffect(battlerDef, EFFECT_TRICK_ROOM))) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + if (!(gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && !HasMoveEffect(battlerDef, EFFECT_TRICK_ROOM)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_MAGIC_ROOM: - if (!((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || HasMoveEffect(battlerDef, EFFECT_MAGIC_ROOM))) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + if (!(gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) && !HasMoveEffect(battlerDef, EFFECT_MAGIC_ROOM)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_WONDER_ROOM: - if (!((gFieldStatuses & STATUS_FIELD_WONDER_ROOM) || HasMoveEffect(battlerDef, EFFECT_WONDER_ROOM))) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + if (!(gFieldStatuses & STATUS_FIELD_WONDER_ROOM) && !HasMoveEffect(battlerDef, EFFECT_WONDER_ROOM)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_GRAVITY: if (!(gFieldStatuses & STATUS_FIELD_GRAVITY)) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_SAFEGUARD: if (!(gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_SAFEGUARD)) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_MIST: if (!(gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_MIST)) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_LIGHT_SCREEN: case EFFECT_REFLECT: case EFFECT_AURORA_VEIL: if (ShouldSetScreen(battlerAtk, battlerDef, moveEffect)) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_SPIKES: case EFFECT_STEALTH_ROCK: case EFFECT_STICKY_WEB: case EFFECT_TOXIC_SPIKES: - if (!(AI_DATA->abilities[battlerDef] == ABILITY_MAGIC_BOUNCE || CountUsablePartyMons(battlerDef) == 0 || HasMoveWithMoveEffect(battlerDef, MOVE_EFFECT_RAPIDSPIN))) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + if (AI_DATA->abilities[battlerDef] != ABILITY_MAGIC_BOUNCE + && CountUsablePartyMons(battlerDef) != 0 + && !HasMoveWithMoveEffect(battlerDef, MOVE_EFFECT_RAPIDSPIN) + && !HasMoveEffect(battlerDef, EFFECT_DEFOG)) + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_GRASSY_TERRAIN: if (!(gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN)) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_ELECTRIC_TERRAIN: if (!(gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN)) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_PSYCHIC_TERRAIN: if (!(gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN)) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_MISTY_TERRAIN: if (!(gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN)) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_SANDSTORM: if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_SANDSTORM | B_WEATHER_PRIMAL_ANY))) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_SUNNY_DAY: if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_SUN | B_WEATHER_PRIMAL_ANY))) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_RAIN_DANCE: if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_RAIN | B_WEATHER_PRIMAL_ANY))) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_HAIL: if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_HAIL | B_WEATHER_PRIMAL_ANY))) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); + ADJUST_SCORE(POWERFUL_STATUS_MOVE); + break; case EFFECT_SNOWSCAPE: if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_SNOW | B_WEATHER_PRIMAL_ANY))) - RETURN_SCORE_PLUS(POWERFUL_STATUS_MOVE); - default: - return FALSE; + ADJUST_SCORE(POWERFUL_STATUS_MOVE); } + return score; } static void AI_Flee(void) diff --git a/src/data/trainers.h b/src/data/trainers.h index 433cac517e9f..1c0a5c32711a 100644 --- a/src/data/trainers.h +++ b/src/data/trainers.h @@ -3196,7 +3196,7 @@ const struct Trainer gTrainers[] = { .trainerName = _("ROXANNE"), .items = {ITEM_POTION, ITEM_POTION, ITEM_NONE, ITEM_NONE}, .doubleBattle = FALSE, - .aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY, + .aiFlags = AI_FLAG_POWERFUL_STATUS, .party = TRAINER_PARTY(sParty_Roxanne1), }, diff --git a/test/battle/ai_powerful_status.c b/test/battle/ai_powerful_status.c index b66f75439394..9fea614cd2f1 100644 --- a/test/battle/ai_powerful_status.c +++ b/test/battle/ai_powerful_status.c @@ -8,8 +8,8 @@ AI_SINGLE_BATTLE_TEST("AI prefers to set up a powerful Status over fainting a ta ASSUME(gMovesInfo[MOVE_TRICK_ROOM].effect == EFFECT_TRICK_ROOM); ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); ASSUME(gMovesInfo[MOVE_TACKLE].power > 0); - AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_POWERFUL_STATUS); - PLAYER(SPECIES_WOBBUFFET); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_POWERFUL_STATUS); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } PLAYER(SPECIES_WYNAUT); OPPONENT(SPECIES_DUSCLOPS) { Moves(MOVE_TRICK_ROOM, MOVE_TACKLE); } } WHEN { @@ -17,6 +17,26 @@ AI_SINGLE_BATTLE_TEST("AI prefers to set up a powerful Status over fainting a ta TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); } } SCENE { - MESSAGE("Wynaut fainted!"); + MESSAGE("Wobbuffet fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI will try to do damage on target instead of setting up hazards if target has a way to remove them") +{ + GIVEN { + AI_LOG; + ASSUME(gMovesInfo[MOVE_TRICK_ROOM].effect == EFFECT_TRICK_ROOM); + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(gMovesInfo[MOVE_TACKLE].power > 0); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_POWERFUL_STATUS | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { HMoves(MOVE_RAPID_SPIN, MOVE_CELEBRATE); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GLIGAR) { Moves(MOVE_STEALTH_ROCK, MOVE_TACKLE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); } + } + SCENE { + MESSAGE("Wobbuffet fainted!"); } } From 31cae83a7e01c7c53eb6a4bb73332b3842ca8189 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 10 Feb 2024 13:36:52 +0100 Subject: [PATCH 3/4] fixed final issues --- include/battle_ai_main.h | 1 - include/config.h | 2 +- include/constants/battle.h | 2 +- src/battle_ai_main.c | 38 ++++++++++++++++++---------- src/battle_ai_util.c | 9 ++++--- src/battle_script_commands.c | 2 +- src/battle_tv.c | 2 +- src/data/moves_info.h | 4 +-- src/data/trainers.h | 2 +- test/battle/ability/shield_dust.c | 2 +- test/battle/ai_check_viability.c | 1 - test/battle/ai_powerful_status.c | 32 +++++++++++++++-------- test/battle/move_effect/rapid_spin.c | 4 +-- 13 files changed, 63 insertions(+), 38 deletions(-) diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index 168d26b0b0ce..18148a89b04e 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -76,7 +76,6 @@ #define RETURN_SCORE_PLUS(val) \ { \ ADJUST_SCORE(val); \ - DebugPrintf("score: %d", score); \ return score; \ } diff --git a/include/config.h b/include/config.h index 17dda42411dc..f4be812643dc 100644 --- a/include/config.h +++ b/include/config.h @@ -6,7 +6,7 @@ // still has them in the ROM. This is because the developers forgot // to define NDEBUG before release, however this has been changed as // Ruby's actual debug build does not use the AGBPrint features. -// #define NDEBUG +#define NDEBUG // To enable printf debugging, comment out "#define NDEBUG". This allows // the various AGBPrint functions to be used. (See include/gba/isagbprint.h). diff --git a/include/constants/battle.h b/include/constants/battle.h index a353fbb35710..6bb5699a5b4a 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -354,7 +354,7 @@ #define MOVE_EFFECT_PREVENT_ESCAPE 33 #define MOVE_EFFECT_NIGHTMARE 34 #define MOVE_EFFECT_ALL_STATS_UP 35 -#define MOVE_EFFECT_RAPIDSPIN 36 +#define MOVE_EFFECT_RAPID_SPIN 36 #define MOVE_EFFECT_REMOVE_STATUS 37 #define MOVE_EFFECT_ATK_DEF_DOWN 38 #define MOVE_EFFECT_ATK_PLUS_2 39 diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 4385ba23f90b..cba710d0f7c9 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -3560,7 +3560,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_LEECH_SEED: if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) || gStatuses3[battlerDef] & STATUS3_LEECHSEED - || HasMoveWithMoveEffect(battlerDef, MOVE_EFFECT_RAPIDSPIN) + || HasMoveWithMoveEffect(battlerDef, MOVE_EFFECT_RAPID_SPIN) || aiData->abilities[battlerDef] == ABILITY_LIQUID_OOZE || aiData->abilities[battlerDef] == ABILITY_MAGIC_GUARD) break; @@ -3719,7 +3719,13 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_STEALTH_ROCK: case EFFECT_STICKY_WEB: case EFFECT_TOXIC_SPIKES: - score += AI_ShouldSetUpHazards(battlerAtk, battlerDef, aiData); + if (AI_ShouldSetUpHazards(battlerAtk, battlerDef, aiData)); + { + if (gDisableStructs[battlerAtk].isFirstTurn) + ADJUST_SCORE(BEST_EFFECT); + else + ADJUST_SCORE(DECENT_EFFECT); + } break; case EFFECT_FORESIGHT: if (aiData->abilities[battlerAtk] == ABILITY_SCRAPPY || aiData->abilities[battlerAtk] == ABILITY_MINDS_EYE) @@ -4252,10 +4258,13 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(GOOD_EFFECT); // Partner might use pledge move break; case EFFECT_TRICK_ROOM: - if (!(gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && GetBattlerSideSpeedAverage(battlerAtk) < GetBattlerSideSpeedAverage(battlerDef)) - ADJUST_SCORE(GOOD_EFFECT); - else if ((gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && GetBattlerSideSpeedAverage(battlerAtk) >= GetBattlerSideSpeedAverage(battlerDef)) - ADJUST_SCORE(GOOD_EFFECT); + if (!(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_POWERFUL_STATUS)) + { + if (!(gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && GetBattlerSideSpeedAverage(battlerAtk) < GetBattlerSideSpeedAverage(battlerDef)) + ADJUST_SCORE(GOOD_EFFECT); + else if ((gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && GetBattlerSideSpeedAverage(battlerAtk) >= GetBattlerSideSpeedAverage(battlerDef)) + ADJUST_SCORE(GOOD_EFFECT); + } break; case EFFECT_MAGIC_ROOM: ADJUST_SCORE(WEAK_EFFECT); @@ -4503,7 +4512,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); } break; - case MOVE_EFFECT_RAPIDSPIN: + case MOVE_EFFECT_RAPID_SPIN: if ((gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY && CountUsablePartyMons(battlerAtk) != 0) || (gStatuses3[battlerAtk] & STATUS3_LEECHSEED || gBattleMons[battlerAtk].status2 & STATUS2_WRAPPED)) ADJUST_SCORE(GOOD_EFFECT); @@ -4636,7 +4645,13 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) break; case MOVE_EFFECT_STEALTH_ROCK: case MOVE_EFFECT_SPIKES: - score += AI_ShouldSetUpHazards(battlerAtk, battlerDef, aiData); + if (AI_ShouldSetUpHazards(battlerAtk, battlerDef, aiData)); + { + if (gDisableStructs[battlerAtk].isFirstTurn) + ADJUST_SCORE(BEST_EFFECT); + else + ADJUST_SCORE(DECENT_EFFECT); + } break; case MOVE_EFFECT_FEINT: if (gMovesInfo[predictedMove].effect == EFFECT_PROTECT) @@ -4652,7 +4667,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) } break; case MOVE_EFFECT_WRAP: - if (!HasMoveWithMoveEffect(battlerDef, MOVE_EFFECT_RAPIDSPIN) && ShouldTrap(battlerAtk, battlerDef, move)) + if (!HasMoveWithMoveEffect(battlerDef, MOVE_EFFECT_RAPID_SPIN) && ShouldTrap(battlerAtk, battlerDef, move)) ADJUST_SCORE(BEST_EFFECT); break; } @@ -5168,10 +5183,7 @@ static s32 AI_PowerfulStatus(u32 battlerAtk, u32 battlerDef, u32 move, s32 score case EFFECT_STEALTH_ROCK: case EFFECT_STICKY_WEB: case EFFECT_TOXIC_SPIKES: - if (AI_DATA->abilities[battlerDef] != ABILITY_MAGIC_BOUNCE - && CountUsablePartyMons(battlerDef) != 0 - && !HasMoveWithMoveEffect(battlerDef, MOVE_EFFECT_RAPIDSPIN) - && !HasMoveEffect(battlerDef, EFFECT_DEFOG)) + if (AI_ShouldSetUpHazards(battlerAtk, battlerDef, AI_DATA)) ADJUST_SCORE(POWERFUL_STATUS_MOVE); break; case EFFECT_GRASSY_TERRAIN: diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index b5db3232fd0f..0da307b45bc4 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -3573,8 +3573,11 @@ bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef) //TODO - track entire opponent party data to determine hazard effectiveness s32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData) { - if (aiData->abilities[battlerDef] == ABILITY_MAGIC_BOUNCE || CountUsablePartyMons(battlerDef) == 0) - return 0; + if (aiData->abilities[battlerDef] == ABILITY_MAGIC_BOUNCE + || CountUsablePartyMons(battlerDef) == 0 + || HasMoveWithMoveEffect(battlerDef, MOVE_EFFECT_RAPID_SPIN) + || HasMoveEffect(battlerDef, EFFECT_DEFOG)) + return FALSE; - return 2 * gDisableStructs[battlerAtk].isFirstTurn; + return TRUE; } diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index de0e478d100b..f4b2385773b2 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -3445,7 +3445,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) gBattlescriptCurrInstr = BattleScript_AllStatsUp; } break; - case MOVE_EFFECT_RAPIDSPIN: + case MOVE_EFFECT_RAPID_SPIN: BattleScriptPush(gBattlescriptCurrInstr + 1); gBattlescriptCurrInstr = BattleScript_RapidSpinAway; break; diff --git a/src/battle_tv.c b/src/battle_tv.c index cb70d1deedd4..80cb0020f81f 100644 --- a/src/battle_tv.c +++ b/src/battle_tv.c @@ -942,7 +942,7 @@ static void AddMovePoints(u8 caseId, u16 arg1, u8 arg2, u8 arg3) // Various cases to add/remove points if (gMovesInfo[arg2].recoil > 0) baseFromEffect++; // Recoil moves - if (MoveHasMoveEffect(arg2, MOVE_EFFECT_RAPIDSPIN)) + if (MoveHasMoveEffect(arg2, MOVE_EFFECT_RAPID_SPIN)) baseFromEffect++; if (MoveHasMoveEffect(arg2, MOVE_EFFECT_SP_ATK_TWO_DOWN) || MoveHasMoveEffect(arg2, MOVE_EFFECT_ATK_DEF_DOWN)) baseFromEffect += 2; // Overheat, Superpower, etc. diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 88873e4f2893..a4e1f3c74390 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -5638,7 +5638,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .category = DAMAGE_CATEGORY_PHYSICAL, .makesContact = TRUE, .additionalEffects = ADDITIONAL_EFFECTS({ - .moveEffect = MOVE_EFFECT_RAPIDSPIN, + .moveEffect = MOVE_EFFECT_RAPID_SPIN, .self = TRUE, } #if B_SPEED_BUFFING_RAPID_SPIN >= GEN_8 @@ -18925,7 +18925,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .sheerForceBoost = TRUE, .makesContact = TRUE, .additionalEffects = ADDITIONAL_EFFECTS({ - .moveEffect = MOVE_EFFECT_RAPIDSPIN, + .moveEffect = MOVE_EFFECT_RAPID_SPIN, .self = TRUE, }, { diff --git a/src/data/trainers.h b/src/data/trainers.h index 1c0a5c32711a..433cac517e9f 100644 --- a/src/data/trainers.h +++ b/src/data/trainers.h @@ -3196,7 +3196,7 @@ const struct Trainer gTrainers[] = { .trainerName = _("ROXANNE"), .items = {ITEM_POTION, ITEM_POTION, ITEM_NONE, ITEM_NONE}, .doubleBattle = FALSE, - .aiFlags = AI_FLAG_POWERFUL_STATUS, + .aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY, .party = TRAINER_PARTY(sParty_Roxanne1), }, diff --git a/test/battle/ability/shield_dust.c b/test/battle/ability/shield_dust.c index 4cd95de61978..cac3dfac4c7f 100644 --- a/test/battle/ability/shield_dust.c +++ b/test/battle/ability/shield_dust.c @@ -94,7 +94,7 @@ SINGLE_BATTLE_TEST("Shield Dust does not block self-targeting effects, primary o GIVEN { ASSUME(MoveHasMoveEffectSelf(MOVE_POWER_UP_PUNCH, MOVE_EFFECT_ATK_PLUS_1) == TRUE); - ASSUME(MoveHasMoveEffectSelf(MOVE_RAPID_SPIN, MOVE_EFFECT_RAPIDSPIN) == TRUE); + ASSUME(MoveHasMoveEffectSelf(MOVE_RAPID_SPIN, MOVE_EFFECT_RAPID_SPIN) == TRUE); ASSUME(MoveHasMoveEffectSelf(MOVE_LEAF_STORM, MOVE_EFFECT_SP_ATK_TWO_DOWN) == TRUE); ASSUME(MoveHasMoveEffectSelf(MOVE_METEOR_ASSAULT, MOVE_EFFECT_RECHARGE) == TRUE); PLAYER(SPECIES_WOBBUFFET); diff --git a/test/battle/ai_check_viability.c b/test/battle/ai_check_viability.c index 69794804c743..a2cd33ed2cb6 100644 --- a/test/battle/ai_check_viability.c +++ b/test/battle/ai_check_viability.c @@ -179,7 +179,6 @@ AI_SINGLE_BATTLE_TEST("AI chooses moves with secondary effect that have a 100% c PARAMETRIZE { ability = ABILITY_SERENE_GRACE; } GIVEN { - AI_LOG; ASSUME(MoveHasMoveEffectWithChance(MOVE_SHADOW_BALL, MOVE_EFFECT_SP_DEF_MINUS_1, 20)); ASSUME(MoveHasMoveEffectWithChance(MOVE_OCTAZOOKA, MOVE_EFFECT_ACC_MINUS_1, 50)); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); diff --git a/test/battle/ai_powerful_status.c b/test/battle/ai_powerful_status.c index 9fea614cd2f1..b625b1126f7a 100644 --- a/test/battle/ai_powerful_status.c +++ b/test/battle/ai_powerful_status.c @@ -6,7 +6,6 @@ AI_SINGLE_BATTLE_TEST("AI prefers to set up a powerful Status over fainting a ta { GIVEN { ASSUME(gMovesInfo[MOVE_TRICK_ROOM].effect == EFFECT_TRICK_ROOM); - ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); ASSUME(gMovesInfo[MOVE_TACKLE].power > 0); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_POWERFUL_STATUS); PLAYER(SPECIES_WOBBUFFET) { HP(1); } @@ -15,8 +14,7 @@ AI_SINGLE_BATTLE_TEST("AI prefers to set up a powerful Status over fainting a ta } WHEN { TURN { EXPECT_MOVE(opponent, MOVE_TRICK_ROOM); } TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); } - } - SCENE { + } SCENE { MESSAGE("Wobbuffet fainted!"); } } @@ -24,19 +22,33 @@ AI_SINGLE_BATTLE_TEST("AI prefers to set up a powerful Status over fainting a ta AI_SINGLE_BATTLE_TEST("AI will try to do damage on target instead of setting up hazards if target has a way to remove them") { GIVEN { - AI_LOG; - ASSUME(gMovesInfo[MOVE_TRICK_ROOM].effect == EFFECT_TRICK_ROOM); - ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(MoveHasMoveEffectSelf(MOVE_RAPID_SPIN, MOVE_EFFECT_RAPID_SPIN) == TRUE); + ASSUME(gMovesInfo[MOVE_STEALTH_ROCK].effect == EFFECT_STEALTH_ROCK); ASSUME(gMovesInfo[MOVE_TACKLE].power > 0); - AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_POWERFUL_STATUS | AI_FLAG_OMNISCIENT); - PLAYER(SPECIES_WOBBUFFET) { HMoves(MOVE_RAPID_SPIN, MOVE_CELEBRATE); } + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_POWERFUL_STATUS | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { HP(1); Moves(MOVE_RAPID_SPIN, MOVE_DEFOG, MOVE_CELEBRATE); } PLAYER(SPECIES_WYNAUT); OPPONENT(SPECIES_GLIGAR) { Moves(MOVE_STEALTH_ROCK, MOVE_TACKLE); } OPPONENT(SPECIES_WYNAUT); } WHEN { TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); } - } - SCENE { + } SCENE { MESSAGE("Wobbuffet fainted!"); } } + +AI_SINGLE_BATTLE_TEST("AI will not set up Rain if it is already raining") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_RAIN_DANCE].effect == EFFECT_RAIN_DANCE); + ASSUME(gMovesInfo[MOVE_TACKLE].power > 0); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_POWERFUL_STATUS); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_RAIN_DANCE, MOVE_TACKLE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_RAIN_DANCE); } + TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); } + } +} diff --git a/test/battle/move_effect/rapid_spin.c b/test/battle/move_effect/rapid_spin.c index 2874aa45270b..1d078ef71e02 100644 --- a/test/battle/move_effect/rapid_spin.c +++ b/test/battle/move_effect/rapid_spin.c @@ -3,11 +3,11 @@ ASSUMPTIONS { - ASSUME(MoveHasMoveEffectSelf(MOVE_RAPID_SPIN, MOVE_EFFECT_RAPIDSPIN) == TRUE); + ASSUME(MoveHasMoveEffectSelf(MOVE_RAPID_SPIN, MOVE_EFFECT_RAPID_SPIN) == TRUE); #if B_SPEED_BUFFING_RAPID_SPIN >= GEN_8 ASSUME(MoveHasMoveEffectSelf(MOVE_RAPID_SPIN, MOVE_EFFECT_SPD_PLUS_1) == TRUE); #endif - ASSUME(MoveHasMoveEffectSelf(MOVE_MORTAL_SPIN, MOVE_EFFECT_RAPIDSPIN) == TRUE); + ASSUME(MoveHasMoveEffectSelf(MOVE_MORTAL_SPIN, MOVE_EFFECT_RAPID_SPIN) == TRUE); ASSUME(MoveHasMoveEffect(MOVE_MORTAL_SPIN, MOVE_EFFECT_POISON) == TRUE); } From b84664410e726bb1f9976352e675ab176ab7624b Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 18 Feb 2024 13:18:51 +0100 Subject: [PATCH 4/4] review changes --- include/battle_ai_util.h | 2 +- include/constants/battle.h | 2 +- include/constants/battle_ai.h | 2 +- src/battle_ai_main.c | 2 ++ src/battle_ai_util.c | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index cfdb791e0ece..2d541ea73e5d 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -189,6 +189,6 @@ s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct Battl s32 AI_CheckMoveEffects(u32 battlerAtk, u32 battlerDef, u32 move, s32 score, struct AiLogicData *aiData, u32 predictedMove, bool32 isDoubleBattle); s32 AI_TryToClearStats(u32 battlerAtk, u32 battlerDef, bool32 isDoubleBattle); bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef); -s32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData); +bool32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData); #endif //GUARD_BATTLE_AI_UTIL_H diff --git a/include/constants/battle.h b/include/constants/battle.h index 6bb5699a5b4a..10634aad43d0 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -354,7 +354,7 @@ #define MOVE_EFFECT_PREVENT_ESCAPE 33 #define MOVE_EFFECT_NIGHTMARE 34 #define MOVE_EFFECT_ALL_STATS_UP 35 -#define MOVE_EFFECT_RAPID_SPIN 36 +#define MOVE_EFFECT_RAPID_SPIN 36 #define MOVE_EFFECT_REMOVE_STATUS 37 #define MOVE_EFFECT_ATK_DEF_DOWN 38 #define MOVE_EFFECT_ATK_PLUS_2 39 diff --git a/include/constants/battle_ai.h b/include/constants/battle_ai.h index 979ca80be43c..2093312ea619 100644 --- a/include/constants/battle_ai.h +++ b/include/constants/battle_ai.h @@ -34,7 +34,7 @@ #define AI_FLAG_PREFER_BATON_PASS (1 << 6) #define AI_FLAG_DOUBLE_BATTLE (1 << 7) // removed, split between AI_FLAG_CHECK_BAD_MOVE & AI_FLAG_CHECK_GOOD_MOVE #define AI_FLAG_HP_AWARE (1 << 8) -#define AI_FLAG_POWERFUL_STATUS (1 << 9) // AI prefers moves that set up field effects or side statuses, even over a kill. Usega with caution +#define AI_FLAG_POWERFUL_STATUS (1 << 9) // AI prefers moves that set up field effects or side statuses, even if the user can faint the target // New, Trainer Handicap Flags #define AI_FLAG_NEGATE_UNAWARE (1 << 10) // AI is NOT aware of negating effects like wonder room, mold breaker, etc #define AI_FLAG_WILL_SUICIDE (1 << 11) // AI will use explosion / self destruct / final gambit / etc diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index cba710d0f7c9..fdf364c79ba7 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -4696,6 +4696,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score // Calculates score based on effects of a move score += AI_CalcMoveScore(battlerAtk, battlerDef, move); + return score; } @@ -5222,6 +5223,7 @@ static s32 AI_PowerfulStatus(u32 battlerAtk, u32 battlerDef, u32 move, s32 score if (!(AI_GetWeather(AI_DATA) & (B_WEATHER_SNOW | B_WEATHER_PRIMAL_ANY))) ADJUST_SCORE(POWERFUL_STATUS_MOVE); } + return score; } diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 0da307b45bc4..4a64f350135d 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -3571,7 +3571,7 @@ bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef) } //TODO - track entire opponent party data to determine hazard effectiveness -s32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData) +bool32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData) { if (aiData->abilities[battlerDef] == ABILITY_MAGIC_BOUNCE || CountUsablePartyMons(battlerDef) == 0