From 1339256d5dd43a079427b757f5528ee8700972e3 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Mon, 25 Dec 2023 01:43:57 +0100 Subject: [PATCH 1/3] Adds Ability Hospitality (#3818) * Adds Ability Hospitality * fix agbcc --- data/battle_scripts_1.s | 10 ++++ include/battle_scripts.h | 1 + include/constants/battle_string_ids.h | 3 +- src/battle_message.c | 2 + src/battle_util.c | 14 ++++++ test/battle/ability/hospitality.c | 70 +++++++++++++++++++++++++++ 6 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 test/battle/ability/hospitality.c diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index b4d9c0c79453..fd19ed79cc6c 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -8853,6 +8853,16 @@ BattleScript_ZeroToHeroActivates:: waitmessage B_WAIT_TIME_LONG end3 +BattleScript_HospitalityActivates:: + pause B_WAIT_TIME_SHORT + call BattleScript_AbilityPopUp + printstring STRINGID_HOSPITALITYRESTORATION + waitmessage B_WAIT_TIME_LONG + orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE + healthbarupdate BS_TARGET + datahpupdate BS_TARGET + end3 + BattleScript_AttackWeakenedByStrongWinds:: pause B_WAIT_TIME_SHORT printstring STRINGID_ATTACKWEAKENEDBSTRONGWINDS diff --git a/include/battle_scripts.h b/include/battle_scripts.h index bc534b69af69..db9db4fb8243 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -461,6 +461,7 @@ extern const u8 BattleScript_CudChewActivates[]; extern const u8 BattleScript_SupremeOverlordActivates[]; extern const u8 BattleScript_CostarActivates[]; extern const u8 BattleScript_ZeroToHeroActivates[]; +extern const u8 BattleScript_HospitalityActivates[]; extern const u8 BattleScript_ToxicDebrisActivates[]; extern const u8 BattleScript_EarthEaterActivates[]; extern const u8 BattleScript_MimicryActivates_End3[]; diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index 6263f87a38b0..5b571c9fb259 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -696,8 +696,9 @@ #define STRINGID_SWAMPENVELOPEDSIDE 694 #define STRINGID_THESWAMPDISAPPEARED 695 #define STRINGID_PKMNTELLCHILLINGRECEPTIONJOKE 696 +#define STRINGID_HOSPITALITYRESTORATION 697 -#define BATTLESTRINGS_COUNT 697 +#define BATTLESTRINGS_COUNT 698 // This is the string id that gBattleStringsTable starts with. // String ids before this (e.g. STRINGID_INTROMSG) are not in the table, diff --git a/src/battle_message.c b/src/battle_message.c index b5e4db71aae8..35ff5379ec3e 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -833,9 +833,11 @@ static const u8 sText_HurtByTheSeaOfFire[] = _("{B_ATK_TEAM1} {B_ATK_NAME_WITH_P static const u8 sText_TheSeaOfFireDisappeared[] = _("The sea of fire around {B_ATK_TEAM2}\nteam disappeared!"); static const u8 sText_SwampEnvelopedSide[] = _("A swamp enveloped\n{B_DEF_TEAM2} team!"); static const u8 sText_TheSwampDisappeared[] = _("The swamp around {B_ATK_TEAM2}\nteam disappeared!"); +static const u8 sText_HospitalityRestoration[] = _("The {B_ATK_PARTNER_NAME} drank down all\nthe matcha that Sinistcha made!"); const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = { + [STRINGID_HOSPITALITYRESTORATION - BATTLESTRINGS_TABLE_START] = sText_HospitalityRestoration, [STRINGID_THESWAMPDISAPPEARED - BATTLESTRINGS_TABLE_START] = sText_TheSwampDisappeared, [STRINGID_SWAMPENVELOPEDSIDE - BATTLESTRINGS_TABLE_START] = sText_SwampEnvelopedSide, [STRINGID_THESEAOFFIREDISAPPEARED - BATTLESTRINGS_TABLE_START] = sText_TheSeaOfFireDisappeared, diff --git a/src/battle_util.c b/src/battle_util.c index 359ba8226b6d..b720c5c83220 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4314,6 +4314,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 u32 moveType, move; u32 side; u32 i, j; + u32 partner, partnerMaxHP; struct Pokemon *mon; if (gBattleTypeFlags & BATTLE_TYPE_SAFARI) @@ -4922,6 +4923,19 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 effect++; } break; + case ABILITY_HOSPITALITY: + partner = BATTLE_PARTNER(battler); + partnerMaxHP = GetNonDynamaxMaxHP(partner); + + if (!gSpecialStatuses[battler].switchInAbilityDone && IsDoubleBattle() && gBattleMons[partner].hp < partnerMaxHP) + { + gBattlerTarget = partner; + gSpecialStatuses[battler].switchInAbilityDone = TRUE; + gBattleMoveDamage = (partnerMaxHP / 4) * -1; + BattleScriptPushCursorAndCallback(BattleScript_HospitalityActivates); + effect++; + } + break; } break; case ABILITYEFFECT_ENDTURN: // 1 diff --git a/test/battle/ability/hospitality.c b/test/battle/ability/hospitality.c new file mode 100644 index 000000000000..8bb78bffc0c2 --- /dev/null +++ b/test/battle/ability/hospitality.c @@ -0,0 +1,70 @@ +#include "global.h" +#include "test/battle.h" + +DOUBLE_BATTLE_TEST("Hospitality user restores 25% of ally's health") +{ + s16 health; + + PARAMETRIZE { health = 75; } + PARAMETRIZE { health = 100; } + + GIVEN { + PLAYER(SPECIES_POLTCHAGEIST) { Ability(ABILITY_HOSPITALITY); } + PLAYER(SPECIES_WOBBUFFET) { HP(health); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + if (health == 75) { + ABILITY_POPUP(playerLeft, ABILITY_HOSPITALITY); + MESSAGE("The Wobbuffet drank down all the matcha that Sinistcha made!"); + HP_BAR(playerRight, damage: -25); + } else { + NONE_OF { + ABILITY_POPUP(playerLeft, ABILITY_HOSPITALITY); + MESSAGE("The Wobbuffet drank down all the matcha that Sinistcha made!"); + HP_BAR(playerRight, damage: -25); + } + } + } +} + +DOUBLE_BATTLE_TEST("Hospitality user restores 25% of ally's health on switch-in") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) + PLAYER(SPECIES_WOBBUFFET) { HP(75); MaxHP(100); } + PLAYER(SPECIES_POLTCHAGEIST) { Ability(ABILITY_HOSPITALITY); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(playerLeft, 2); } + } SCENE { + MESSAGE("Wobbuffet, that's enough! Come back!"); + MESSAGE("Go! Ptchageist!"); + ABILITY_POPUP(playerLeft, ABILITY_HOSPITALITY); + MESSAGE("The Wobbuffet drank down all the matcha that Sinistcha made!"); + HP_BAR(playerRight, damage: -25); + } +} + +DOUBLE_BATTLE_TEST("Hospitality ignores Substitute") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_POLTCHAGEIST) { Ability(ABILITY_HOSPITALITY); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_SUBSTITUTE); } + TURN { SWITCH(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, playerRight); + MESSAGE("Wobbuffet, that's enough! Come back!"); + MESSAGE("Go! Ptchageist!"); + ABILITY_POPUP(playerLeft, ABILITY_HOSPITALITY); + MESSAGE("The Wobbuffet drank down all the matcha that Sinistcha made!"); + } +} From 166a1a4e63b22e16f3e8f76e0921a60ccda865db Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Mon, 25 Dec 2023 18:28:16 +0100 Subject: [PATCH 2/3] Adds ability Embody Aspect + minor fix to Hospitality (#3821) * Adds ability Embody Aspect + minor fix to Hospitality * comment out failing tests related to neutralizing gas * fixes neutralizing gas bug * leftover --- data/battle_scripts_1.s | 1 + src/battle_util.c | 37 ++++++++++++++-- test/battle/ability/dauntless_shield.c | 21 ++++++++- test/battle/ability/embody_aspect.c | 59 ++++++++++++++++++++++++++ test/battle/ability/intrepid_sword.c | 21 ++++++++- test/battle/move_effect/hit_escape.c | 1 - 6 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 test/battle/ability/embody_aspect.c diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index fd19ed79cc6c..09eb96916867 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -9259,6 +9259,7 @@ BattleScript_BattlerAbilityStatRaiseOnSwitchIn:: waitanimation printstring STRINGID_BATTLERABILITYRAISEDSTAT waitmessage B_WAIT_TIME_LONG + copybyte gBattlerAttacker, sSAVED_BATTLER end3 BattleScript_ScriptingAbilityStatRaise:: diff --git a/src/battle_util.c b/src/battle_util.c index b720c5c83220..da42b4001039 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4314,7 +4314,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 u32 moveType, move; u32 side; u32 i, j; - u32 partner, partnerMaxHP; + u32 partner; struct Pokemon *mon; if (gBattleTypeFlags & BATTLE_TYPE_SAFARI) @@ -4800,6 +4800,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (!gSpecialStatuses[battler].switchInAbilityDone && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN) && !(gBattleStruct->intrepidSwordBoost[GetBattlerSide(battler)] & gBitTable[gBattlerPartyIndexes[battler]])) { + gBattleScripting.savedBattler = gBattlerAttacker; gBattlerAttacker = battler; if (B_INTREPID_SWORD == GEN_9) gBattleStruct->intrepidSwordBoost[GetBattlerSide(battler)] |= gBitTable[gBattlerPartyIndexes[battler]]; @@ -4813,6 +4814,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (!gSpecialStatuses[battler].switchInAbilityDone && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN) && !(gBattleStruct->dauntlessShieldBoost[GetBattlerSide(battler)] & gBitTable[gBattlerPartyIndexes[battler]])) { + gBattleScripting.savedBattler = gBattlerAttacker; gBattlerAttacker = battler; if (B_DAUNTLESS_SHIELD == GEN_9) gBattleStruct->dauntlessShieldBoost[GetBattlerSide(battler)] |= gBitTable[gBattlerPartyIndexes[battler]]; @@ -4925,17 +4927,44 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 break; case ABILITY_HOSPITALITY: partner = BATTLE_PARTNER(battler); - partnerMaxHP = GetNonDynamaxMaxHP(partner); - if (!gSpecialStatuses[battler].switchInAbilityDone && IsDoubleBattle() && gBattleMons[partner].hp < partnerMaxHP) + if (!gSpecialStatuses[battler].switchInAbilityDone && IsDoubleBattle() && gBattleMons[partner].hp < gBattleMons[partner].maxHP) { gBattlerTarget = partner; gSpecialStatuses[battler].switchInAbilityDone = TRUE; - gBattleMoveDamage = (partnerMaxHP / 4) * -1; + gBattleMoveDamage = (GetNonDynamaxMaxHP(partner) / 4) * -1; BattleScriptPushCursorAndCallback(BattleScript_HospitalityActivates); effect++; } break; + case ABILITY_EMBODY_ASPECT_TEAL: + case ABILITY_EMBODY_ASPECT_HEARTHFLAME: + case ABILITY_EMBODY_ASPECT_WELLSPRING: + case ABILITY_EMBODY_ASPECT_CORNERSTONE: + if (!gSpecialStatuses[battler].switchInAbilityDone) + { + u32 stat = STAT_SPATK; + + if (gLastUsedAbility == ABILITY_EMBODY_ASPECT_TEAL) + stat = STAT_SPATK; + else if (gLastUsedAbility == ABILITY_EMBODY_ASPECT_HEARTHFLAME) + stat = STAT_ATK; + else if (gLastUsedAbility == ABILITY_EMBODY_ASPECT_WELLSPRING) + stat = STAT_SPDEF; + else if (gLastUsedAbility == ABILITY_EMBODY_ASPECT_CORNERSTONE) + stat = STAT_DEF; + + if (CompareStat(battler, stat, MAX_STAT_STAGE, CMP_EQUAL)) + break; + + gBattleScripting.savedBattler = gBattlerAttacker; + gBattlerAttacker = battler; + gSpecialStatuses[battler].switchInAbilityDone = TRUE; + SET_STATCHANGER(stat, 1, FALSE); + BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn); + effect++; + } + break; } break; case ABILITYEFFECT_ENDTURN: // 1 diff --git a/test/battle/ability/dauntless_shield.c b/test/battle/ability/dauntless_shield.c index eb7b5c15cb18..b646d00b0de1 100644 --- a/test/battle/ability/dauntless_shield.c +++ b/test/battle/ability/dauntless_shield.c @@ -3,7 +3,6 @@ ASSUMPTIONS { - ASSUME(P_GEN_8_POKEMON == TRUE); ASSUME(B_PROTEAN_LIBERO == GEN_9); } @@ -45,3 +44,23 @@ SINGLE_BATTLE_TEST("Dauntless Shield raises Attack by one stage only once per ba EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); } } + +SINGLE_BATTLE_TEST("Dauntless Shield activates when it's no longer effected by Neutralizing Gas") +{ + GIVEN { + PLAYER(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZAMAZENTA) { Ability(ABILITY_DAUNTLESS_SHIELD); } + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_NEUTRALIZING_GAS); + MESSAGE("Neutralizing Gas filled the area!"); + MESSAGE("Weezing, that's enough! Come back!"); + MESSAGE("The effects of Neutralizing Gas wore off!"); + ABILITY_POPUP(opponent, ABILITY_DAUNTLESS_SHIELD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Zamazenta's Dauntless Shield raised its Defense!"); + } +} + diff --git a/test/battle/ability/embody_aspect.c b/test/battle/ability/embody_aspect.c new file mode 100644 index 000000000000..8b37dd1a7477 --- /dev/null +++ b/test/battle/ability/embody_aspect.c @@ -0,0 +1,59 @@ +#include "global.h" +#include "test/battle.h" + + +SINGLE_BATTLE_TEST("Embodoy Aspect raises a stat depending on the users form by one stage") +{ + u16 species, ability; + + PARAMETRIZE { species = SPECIES_OGERPON_TEAL_MASK_TERA; ability = ABILITY_EMBODY_ASPECT_TEAL; } + PARAMETRIZE { species = SPECIES_OGERPON_HEARTHFLAME_MASK_TERA; ability = ABILITY_EMBODY_ASPECT_HEARTHFLAME; } + PARAMETRIZE { species = SPECIES_OGERPON_WELLSPRING_MASK_TERA; ability = ABILITY_EMBODY_ASPECT_WELLSPRING; } + PARAMETRIZE { species = SPECIES_OGERPON_CORNERSTONE_MASK_TERA; ability = ABILITY_EMBODY_ASPECT_CORNERSTONE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ability); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + if (ability == ABILITY_EMBODY_ASPECT_TEAL) + MESSAGE("Foe Ogerpon's Embody Aspect raised its Sp. Atk!"); + else if (ability == ABILITY_EMBODY_ASPECT_HEARTHFLAME) + MESSAGE("Foe Ogerpon's Embody Aspect raised its Attack!"); + else if (ability == ABILITY_EMBODY_ASPECT_WELLSPRING) + MESSAGE("Foe Ogerpon's Embody Aspect raised its Sp. Def!"); + else if (ability == ABILITY_EMBODY_ASPECT_CORNERSTONE) + MESSAGE("Foe Ogerpon's Embody Aspect raised its Defense!"); + } THEN { + if (ability == ABILITY_EMBODY_ASPECT_TEAL) + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + else if (ability == ABILITY_EMBODY_ASPECT_HEARTHFLAME) + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + else if (ability == ABILITY_EMBODY_ASPECT_WELLSPRING) + EXPECT_EQ(opponent->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + else if (ability == ABILITY_EMBODY_ASPECT_CORNERSTONE) + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Embodoy Aspect activates when it's no longer effected by Neutralizing Gas") +{ + GIVEN { + PLAYER(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_OGERPON_TEAL_MASK_TERA) { Ability(ABILITY_EMBODY_ASPECT_TEAL); } + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_NEUTRALIZING_GAS); + MESSAGE("Neutralizing Gas filled the area!"); + MESSAGE("Weezing, that's enough! Come back!"); + MESSAGE("The effects of Neutralizing Gas wore off!"); + ABILITY_POPUP(opponent, ABILITY_EMBODY_ASPECT_TEAL); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Ogerpon's Embody Aspect raised its Sp. Atk!"); + } +} diff --git a/test/battle/ability/intrepid_sword.c b/test/battle/ability/intrepid_sword.c index 88228bbe784a..9f2901a8d2aa 100644 --- a/test/battle/ability/intrepid_sword.c +++ b/test/battle/ability/intrepid_sword.c @@ -3,7 +3,6 @@ ASSUMPTIONS { - ASSUME(P_GEN_8_POKEMON == TRUE); ASSUME(B_INTREPID_SWORD == GEN_9); } @@ -45,3 +44,23 @@ SINGLE_BATTLE_TEST("Intrepid Sword raises Attack by one stage only once per batt EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); } } + +SINGLE_BATTLE_TEST("Intrepid Sword activates when it's no longer effected by Neutralizing Gas") +{ + GIVEN { + PLAYER(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); } + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_NEUTRALIZING_GAS); + MESSAGE("Neutralizing Gas filled the area!"); + MESSAGE("Weezing, that's enough! Come back!"); + MESSAGE("The effects of Neutralizing Gas wore off!"); + ABILITY_POPUP(opponent, ABILITY_INTREPID_SWORD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Zacian's Intrepid Sword raised its Attack!"); + } +} + diff --git a/test/battle/move_effect/hit_escape.c b/test/battle/move_effect/hit_escape.c index b9d1bc99e72c..49a8c58627d0 100644 --- a/test/battle/move_effect/hit_escape.c +++ b/test/battle/move_effect/hit_escape.c @@ -98,7 +98,6 @@ SINGLE_BATTLE_TEST("U-turn switches the user out if Wimp Out fails to activate") SINGLE_BATTLE_TEST("U-turn switches the user out after Ice Face activates") { GIVEN { - ASSUME(P_GEN_8_POKEMON == TRUE); PLAYER(SPECIES_BEEDRILL); PLAYER(SPECIES_WYNAUT); OPPONENT(SPECIES_EISCUE) { Ability(ABILITY_ICE_FACE); } From a9d6832908cc209899a8f189d3945d4720f6d0e8 Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Wed, 27 Dec 2023 07:54:37 -0500 Subject: [PATCH 3/3] Separate AI flags by battler position (#3003) * ai flags by battlerId * fix recoded battle saved ai flags * update aiFlags check in OpponentHandleChoosePokemon * add header for TRAINER_CUSTOM_PARTNER define * initialize flags in BattleAI_SetupAIData * fix usage of TRAINER_CUSTOM_PARTNER * remove whitespace --------- Co-authored-by: ghoulslash --- include/battle.h | 2 +- src/battle_ai_main.c | 126 ++++++++++++++++++++----------- src/battle_ai_switch_items.c | 20 ++--- src/battle_ai_util.c | 66 +++++++++------- src/battle_controller_opponent.c | 2 +- src/battle_debug.c | 4 +- src/recorded_battle.c | 2 +- 7 files changed, 139 insertions(+), 83 deletions(-) diff --git a/include/battle.h b/include/battle.h index c41698fd5f55..3fe22a4377b8 100644 --- a/include/battle.h +++ b/include/battle.h @@ -336,7 +336,7 @@ struct AI_ThinkingStruct u16 moveConsidered; s32 score[MAX_MON_MOVES]; u32 funcResult; - u32 aiFlags; + u32 aiFlags[MAX_BATTLERS_COUNT]; u8 aiAction; u8 aiLogicId; struct AI_SavedBattleMon saved[MAX_BATTLERS_COUNT]; diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 8980691356a9..abe780db68ac 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -22,6 +22,7 @@ #include "constants/hold_effects.h" #include "constants/moves.h" #include "constants/items.h" +#include "constants/trainers.h" #define AI_ACTION_DONE (1 << 0) #define AI_ACTION_FLEE (1 << 1) @@ -139,36 +140,76 @@ static u32 GetWildAiFlags(void) return flags; } +static u32 GetAiFlags(u16 trainerId) +{ + u32 flags = 0; + + if (!(gBattleTypeFlags & BATTLE_TYPE_HAS_AI) && !IsWildMonSmart()) + return 0; + if (trainerId == 0xFFFF) + { + flags = GetWildAiFlags(); + } + else + { + if (gBattleTypeFlags & BATTLE_TYPE_RECORDED) + flags = GetAiScriptsInRecordedBattle(); + else if (gBattleTypeFlags & BATTLE_TYPE_SAFARI) + flags = AI_FLAG_SAFARI; + else if (gBattleTypeFlags & BATTLE_TYPE_ROAMER) + flags = AI_FLAG_ROAMING; + else if (gBattleTypeFlags & BATTLE_TYPE_FIRST_BATTLE) + flags = AI_FLAG_FIRST_BATTLE; + else if (gBattleTypeFlags & BATTLE_TYPE_FACTORY) + flags = GetAiScriptsInBattleFactory(); + else if (gBattleTypeFlags & (BATTLE_TYPE_FRONTIER | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_TRAINER_HILL | BATTLE_TYPE_SECRET_BASE)) + flags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT; + else + flags = gTrainers[trainerId].aiFlags; + } + + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + flags |= AI_FLAG_DOUBLE_BATTLE; + + return flags; +} + void BattleAI_SetupFlags(void) { + AI_THINKING_STRUCT->aiFlags[B_POSITION_PLAYER_LEFT] = 0; // player has no AI + #if DEBUG_OVERWORLD_MENU == TRUE if (gIsDebugBattle) - AI_THINKING_STRUCT->aiFlags = gDebugAIFlags; - else + { + AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_LEFT] = gDebugAIFlags; + AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_RIGHT] = gDebugAIFlags; + return; + } #endif - if (gBattleTypeFlags & BATTLE_TYPE_RECORDED) - AI_THINKING_STRUCT->aiFlags = GetAiScriptsInRecordedBattle(); - else if (gBattleTypeFlags & BATTLE_TYPE_SAFARI) - AI_THINKING_STRUCT->aiFlags = AI_FLAG_SAFARI; - else if (gBattleTypeFlags & BATTLE_TYPE_ROAMER) - AI_THINKING_STRUCT->aiFlags = AI_FLAG_ROAMING; - else if (gBattleTypeFlags & BATTLE_TYPE_FIRST_BATTLE) - AI_THINKING_STRUCT->aiFlags = AI_FLAG_FIRST_BATTLE; - else if (gBattleTypeFlags & BATTLE_TYPE_FACTORY) - AI_THINKING_STRUCT->aiFlags = GetAiScriptsInBattleFactory(); - else if (gBattleTypeFlags & (BATTLE_TYPE_FRONTIER | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_TRAINER_HILL | BATTLE_TYPE_SECRET_BASE)) - AI_THINKING_STRUCT->aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT; - else if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) - AI_THINKING_STRUCT->aiFlags = gTrainers[gTrainerBattleOpponent_A].aiFlags | gTrainers[gTrainerBattleOpponent_B].aiFlags; + + if (IsWildMonSmart() && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_TRAINER))) + { + // smart wild AI + AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_LEFT] = GetAiFlags(0xFFFF); + AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_RIGHT] = GetAiFlags(0xFFFF); + } else - AI_THINKING_STRUCT->aiFlags = gTrainers[gTrainerBattleOpponent_A].aiFlags; - - // check smart wild AI - if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_TRAINER)) && IsWildMonSmart()) - AI_THINKING_STRUCT->aiFlags |= GetWildAiFlags(); - - if (gBattleTypeFlags & (BATTLE_TYPE_DOUBLE | BATTLE_TYPE_TWO_OPPONENTS) || gTrainers[gTrainerBattleOpponent_A].doubleBattle) - AI_THINKING_STRUCT->aiFlags |= AI_FLAG_DOUBLE_BATTLE; // Act smart in doubles and don't attack your partner. + { + AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_LEFT] = GetAiFlags(gTrainerBattleOpponent_A); + if (gTrainerBattleOpponent_B != 0) + AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_RIGHT] = GetAiFlags(gTrainerBattleOpponent_B); + else + AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_RIGHT] = AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_LEFT]; + } + + if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) + { + AI_THINKING_STRUCT->aiFlags[B_POSITION_PLAYER_RIGHT] = GetAiFlags(gPartnerTrainerId - TRAINER_PARTNER(PARTNER_NONE)); + } + else + { + AI_THINKING_STRUCT->aiFlags[B_POSITION_PLAYER_RIGHT] = 0; // player + } } // sBattler_AI set in ComputeBattleAiScores @@ -176,11 +217,12 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) { s32 i; u8 moveLimitations; + u32 flags[MAX_BATTLERS_COUNT]; // Clear AI data but preserve the flags. - u32 flags = AI_THINKING_STRUCT->aiFlags; + memcpy(&flags[0], &AI_THINKING_STRUCT->aiFlags[0], sizeof(u32) * MAX_BATTLERS_COUNT); memset(AI_THINKING_STRUCT, 0, sizeof(struct AI_ThinkingStruct)); - AI_THINKING_STRUCT->aiFlags = flags; + memcpy(&AI_THINKING_STRUCT->aiFlags[0], &flags[0], sizeof(u32) * MAX_BATTLERS_COUNT); // Conditional score reset, unlike Ruby. for (i = 0; i < MAX_MON_MOVES; i++) @@ -251,7 +293,7 @@ static void CopyBattlerDataToAIParty(u32 bPosition, u32 side) void Ai_InitPartyStruct(void) { u32 i; - bool32 isOmniscient = (AI_THINKING_STRUCT->aiFlags & AI_FLAG_OMNISCIENT); + bool32 isOmniscient = (AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_OMNISCIENT) || (AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_OMNISCIENT); struct Pokemon *mon; AI_PARTY->count[B_SIDE_PLAYER] = gPlayerPartyCount; @@ -433,13 +475,13 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle) if (CountUsablePartyMons(battler) > 0 && !IsBattlerTrapped(battler, TRUE) && !(gBattleTypeFlags & (BATTLE_TYPE_ARENA | BATTLE_TYPE_PALACE)) - && AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS)) + && AI_THINKING_STRUCT->aiFlags[battler] & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS)) { // Consider switching if all moves are worthless to use. if (GetTotalBaseStat(gBattleMons[battler].species) >= 310 // Mon is not weak. && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2) // Mon has more than 50% of its HP { - s32 cap = AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93; + s32 cap = AI_THINKING_STRUCT->aiFlags[battler] & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93; if (doubleBattle) { for (i = 0; i < MAX_BATTLERS_COUNT; i++) @@ -492,7 +534,7 @@ static u32 ChooseMoveOrAction_Singles(u32 battlerAi) u8 consideredMoveArray[MAX_MON_MOVES]; u32 numOfBestMoves; s32 i; - u32 flags = AI_THINKING_STRUCT->aiFlags; + u32 flags = AI_THINKING_STRUCT->aiFlags[battlerAi]; AI_DATA->partnerMove = 0; // no ally while (flags != 0) @@ -577,7 +619,7 @@ static u32 ChooseMoveOrAction_Doubles(u32 battlerAi) AI_DATA->partnerMove = GetAllyChosenMove(battlerAi); AI_THINKING_STRUCT->aiLogicId = 0; AI_THINKING_STRUCT->movesetIndex = 0; - flags = AI_THINKING_STRUCT->aiFlags; + flags = AI_THINKING_STRUCT->aiFlags[sBattler_AI]; while (flags != 0) { @@ -1017,7 +1059,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_EXPLOSION: - if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_WILL_SUICIDE)) + if (!(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_WILL_SUICIDE)) ADJUST_SCORE(-2); if (effectiveness == AI_EFFECTIVENESS_x0) @@ -1996,7 +2038,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } } - /*if (AI_THINKING_STRUCT->aiFlags == AI_SCRIPT_CHECK_BAD_MOVE //Only basic AI + /*if (AI_THINKING_STRUCT->aiFlags[battlerAtk] == AI_SCRIPT_CHECK_BAD_MOVE //Only basic AI && IS_DOUBLE_BATTLE) //Make the regular AI know how to use Protect minimally in Doubles { u8 shouldProtect = ShouldProtect(battlerAtk, battlerDef, move); @@ -2803,7 +2845,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) switch (atkPartnerAbility) { case ABILITY_VOLT_ABSORB: - if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_HP_AWARE)) + if (!(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_HP_AWARE)) { RETURN_SCORE_MINUS(10); } @@ -2824,7 +2866,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case ABILITY_WATER_ABSORB: case ABILITY_DRY_SKIN: - if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_HP_AWARE)) + if (!(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_HP_AWARE)) { RETURN_SCORE_MINUS(10); } @@ -3233,7 +3275,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score } // check status move preference - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_PREFER_STATUS_MOVES && IS_MOVE_STATUS(move) && effectiveness != AI_EFFECTIVENESS_x0) + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_PREFER_STATUS_MOVES && IS_MOVE_STATUS(move) && effectiveness != AI_EFFECTIVENESS_x0) ADJUST_SCORE(1); // check thawing moves @@ -3241,7 +3283,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score ADJUST_SCORE(10); // check burn / frostbite - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING && AI_DATA->abilities[battlerAtk] == ABILITY_NATURAL_CURE) + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_SMART_SWITCHING && AI_DATA->abilities[battlerAtk] == ABILITY_NATURAL_CURE) { if ((gBattleMons[battlerAtk].status1 & STATUS1_BURN && HasOnlyMovesWithCategory(battlerAtk, BATTLE_CATEGORY_PHYSICAL, TRUE)) || (gBattleMons[battlerAtk].status1 & STATUS1_FROSTBITE && HasOnlyMovesWithCategory(battlerAtk, BATTLE_CATEGORY_SPECIAL, TRUE))) @@ -3263,7 +3305,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score ADJUST_SCORE(2); case EFFECT_EXPLOSION: case EFFECT_MEMENTO: - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_WILL_SUICIDE && gBattleMons[battlerDef].statStages[STAT_EVASION] < 7) + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_WILL_SUICIDE && gBattleMons[battlerDef].statStages[STAT_EVASION] < 7) { if (aiData->hpPercents[battlerAtk] < 50 && AI_RandLessThan(128)) ADJUST_SCORE(1); @@ -3569,7 +3611,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score ADJUST_SCORE(5); if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_LIGHT_CLAY) ADJUST_SCORE(2); - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SCREENER) + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_SCREENER) ADJUST_SCORE(2); } break; @@ -3611,7 +3653,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score ADJUST_SCORE(5); break; case EFFECT_MIST: - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SCREENER) + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_SCREENER) ADJUST_SCORE(2); break; case EFFECT_FOCUS_ENERGY: @@ -4070,7 +4112,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score break; case STAT_DEF: case STAT_SPDEF: - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_STALL) + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_STALL) ADJUST_SCORE(1); break; } @@ -4868,7 +4910,7 @@ static s32 AI_SetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32 score || gBattleResults.battleTurnCounter != 0) return score; - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_SMART_SWITCHING && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER && CanTargetFaintAi(battlerDef, battlerAtk) && GetMovePriority(battlerAtk, move) == 0) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 7436f7af6d0a..5f96ef1e34ee 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -38,10 +38,10 @@ static void InitializeSwitchinCandidate(struct Pokemon *mon) static bool32 IsAceMon(u32 battler, u32 monPartyId) { - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON - && !(gBattleStruct->forcedSwitch & gBitTable[battler]) - && monPartyId == CalculateEnemyPartyCount()-1) - return TRUE; + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_ACE_POKEMON + && !(gBattleStruct->forcedSwitch & gBitTable[battler]) + && monPartyId == CalculateEnemyPartyCount()-1) + return TRUE; return FALSE; } @@ -77,7 +77,7 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult) u16 typeEffectiveness = UQ_4_12(1.0), aiMoveEffect; //baseline typing damage // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer - if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING)) + if (!(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) return FALSE; // Won't bother configuring this for double battles @@ -433,7 +433,7 @@ static bool32 ShouldSwitchIfGameStatePrompt(u32 battler, bool32 emitResult) && monAbility != ABILITY_SOUNDPROOF) switchMon = TRUE; - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING) + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_SWITCHING) { //Yawn if (gStatuses3[battler] & STATUS3_YAWN @@ -851,7 +851,7 @@ static bool32 CanMonSurviveHazardSwitchin(u32 battler) static bool32 ShouldSwitchIfEncored(u32 battler, bool32 emitResult) { // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer - if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING)) + if (!(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) return FALSE; // If not Encored or if no good switchin, don't switch @@ -877,7 +877,7 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult) s8 spAttackingStage = gBattleMons[battler].statStages[STAT_SPATK]; // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer - if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING)) + if (!(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) return FALSE; // Physical attacker @@ -1015,7 +1015,7 @@ bool32 ShouldSwitch(u32 battler, bool32 emitResult) return TRUE; //These Functions can prompt switch to generic pary members - if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING) && (CanMonSurviveHazardSwitchin(battler) == FALSE)) + if ((AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_SWITCHING) && (CanMonSurviveHazardSwitchin(battler) == FALSE)) return FALSE; if (ShouldSwitchIfAllBadMoves(battler, emitResult)) return TRUE; @@ -1939,7 +1939,7 @@ u8 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd) // Split ideal mon decision between after previous mon KO'd (prioritize offensive options) and after switching active mon out (prioritize defensive options), and expand the scope of both. // Only use better mon selection if AI_FLAG_SMART_MON_CHOICES is set for the trainer. - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_MON_CHOICES) + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_MON_CHOICES) { bestMonId = GetBestMonIntegrated(party, firstId, lastId, battler, opposingBattler, battlerIn1, battlerIn2, switchAfterMonKOd); return bestMonId; diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 1ad43434424b..2cb7878d420e 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -404,7 +404,7 @@ bool32 BattlerHasAi(u32 battlerId) bool32 IsAiBattlerAware(u32 battlerId) { - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_OMNISCIENT) + if (AI_THINKING_STRUCT->aiFlags[battlerId] & AI_FLAG_OMNISCIENT) return TRUE; return BattlerHasAi(battlerId); @@ -1290,7 +1290,7 @@ u32 AI_DecideHoldEffectForTurn(u32 battlerId) else holdEffect = GetBattlerHoldEffect(battlerId, FALSE); - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_NEGATE_UNAWARE) + if (AI_THINKING_STRUCT->aiFlags[battlerId] & AI_FLAG_NEGATE_UNAWARE) return holdEffect; if (gStatuses3[battlerId] & STATUS3_EMBARGO) @@ -1341,7 +1341,7 @@ bool32 AI_IsBattlerGrounded(u32 battlerId) bool32 DoesBattlerIgnoreAbilityChecks(u32 atkAbility, u32 move) { - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_NEGATE_UNAWARE) + if (AI_THINKING_STRUCT->aiFlags[sBattler_AI] & AI_FLAG_NEGATE_UNAWARE) return FALSE; // AI handicap flag: doesn't understand ability suppression concept if (IsMoldBreakerTypeAbility(atkAbility) || gBattleMoves[move].ignoresTargetAbility) @@ -1352,7 +1352,7 @@ bool32 DoesBattlerIgnoreAbilityChecks(u32 atkAbility, u32 move) static inline bool32 AI_WeatherHasEffect(struct AiLogicData *aiData) { - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_NEGATE_UNAWARE) + if (AI_THINKING_STRUCT->aiFlags[sBattler_AI] & AI_FLAG_NEGATE_UNAWARE) return TRUE; // AI doesn't understand weather supression (handicap) return aiData->weatherHasEffect; // weather damping abilities are announced @@ -1437,7 +1437,7 @@ bool32 IsHazardMoveEffect(u32 moveEffect) bool32 IsMoveRedirectionPrevented(u32 move, u32 atkAbility) { - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_NEGATE_UNAWARE) + if (AI_THINKING_STRUCT->aiFlags[sBattler_AI] & AI_FLAG_NEGATE_UNAWARE) return FALSE; if (move == MOVE_SKY_DROP @@ -1766,7 +1766,9 @@ u32 CountNegativeStatStages(u32 battlerId) bool32 ShouldLowerAttack(u32 battlerAtk, u32 battlerDef, u32 defAbility) { - if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) + && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_ATK] > 4 @@ -1783,7 +1785,9 @@ bool32 ShouldLowerAttack(u32 battlerAtk, u32 battlerDef, u32 defAbility) bool32 ShouldLowerDefense(u32 battlerAtk, u32 battlerDef, u32 defAbility) { - if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) + && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_DEF] > 4 @@ -1800,7 +1804,9 @@ bool32 ShouldLowerDefense(u32 battlerAtk, u32 battlerDef, u32 defAbility) bool32 ShouldLowerSpeed(u32 battlerAtk, u32 battlerDef, u32 defAbility) { - if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) + && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (!AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) @@ -1815,7 +1821,9 @@ bool32 ShouldLowerSpeed(u32 battlerAtk, u32 battlerDef, u32 defAbility) bool32 ShouldLowerSpAtk(u32 battlerAtk, u32 battlerDef, u32 defAbility) { - if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) + && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_SPATK] > 4 @@ -1831,7 +1839,9 @@ bool32 ShouldLowerSpAtk(u32 battlerAtk, u32 battlerDef, u32 defAbility) bool32 ShouldLowerSpDef(u32 battlerAtk, u32 battlerDef, u32 defAbility) { - if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) + && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_SPDEF] > 4 @@ -1847,7 +1857,9 @@ bool32 ShouldLowerSpDef(u32 battlerAtk, u32 battlerDef, u32 defAbility) bool32 ShouldLowerAccuracy(u32 battlerAtk, u32 battlerDef, u32 defAbility) { - if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) + && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (defAbility != ABILITY_CONTRARY @@ -1864,7 +1876,9 @@ bool32 ShouldLowerAccuracy(u32 battlerAtk, u32 battlerDef, u32 defAbility) bool32 ShouldLowerEvasion(u32 battlerAtk, u32 battlerDef, u32 defAbility) { - if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) + && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_EVASION] > DEFAULT_STAT_STAGE @@ -2969,7 +2983,7 @@ bool32 ShouldTrap(u32 battlerAtk, u32 battlerDef, u32 move) if (BattlerWillFaintFromSecondaryDamage(battlerDef, AI_DATA->abilities[battlerDef])) return TRUE; // battler is taking secondary damage with low HP - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_STALL) + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_STALL) { if (!CanTargetFaintAi(battlerDef, battlerAtk)) return TRUE; // attacker goes first and opponent can't kill us @@ -3553,7 +3567,7 @@ void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score) if (AI_DATA->hpPercents[battlerAtk] < 80 && AI_RandLessThan(128)) return; - if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if ((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return; // Damaging moves would get a score boost from AI_TryToFaint or PreferStrongestMove so we don't consider them here switch (statId) @@ -3627,8 +3641,8 @@ void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score) void IncreasePoisonScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) { - if (((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) - || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_PSN || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) + if (((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_PSN || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) return; if (AI_CanPoison(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], move, AI_DATA->partnerMove) && AI_DATA->hpPercents[battlerDef] > 20) @@ -3636,7 +3650,7 @@ void IncreasePoisonScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) if (!HasDamagingMove(battlerDef)) ADJUST_SCORE_PTR(2); - if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_STALL && HasMoveEffect(battlerAtk, EFFECT_PROTECT)) + if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_STALL && HasMoveEffect(battlerAtk, EFFECT_PROTECT)) ADJUST_SCORE_PTR(1); // stall tactic if (HasMoveEffect(battlerAtk, EFFECT_VENOSHOCK) @@ -3651,8 +3665,8 @@ void IncreasePoisonScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) void IncreaseBurnScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) { - if (((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) - || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_BRN || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) + if (((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_BRN || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) return; if (AI_CanBurn(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, AI_DATA->partnerMove)) @@ -3671,8 +3685,8 @@ void IncreaseBurnScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) void IncreaseParalyzeScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) { - if (((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) - || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_PAR || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) + if (((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_PAR || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) return; if (AI_CanParalyze(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], move, AI_DATA->partnerMove)) @@ -3693,8 +3707,8 @@ void IncreaseParalyzeScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) void IncreaseSleepScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) { - if (((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) - || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_SLP || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) + if (((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_SLP || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) return; if (AI_CanPutToSleep(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], move, AI_DATA->partnerMove)) @@ -3712,8 +3726,8 @@ void IncreaseSleepScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) void IncreaseConfusionScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) { - if (((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) - || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_CONFUSION || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) + if (((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_CONFUSION || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) return; if (AI_CanConfuse(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, AI_DATA->partnerMove) @@ -3731,7 +3745,7 @@ void IncreaseConfusionScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score void IncreaseFrostbiteScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) { - if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if ((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return; if (AI_CanGiveFrostbite(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, AI_DATA->partnerMove)) diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c index 649240ce34da..7ffcd5ae2c7d 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -679,7 +679,7 @@ static void OpponentHandleChoosePokemon(u32 battler) if (IsValidForBattle(&gEnemyParty[chosenMonId]) && chosenMonId != gBattlerPartyIndexes[battler1] && chosenMonId != gBattlerPartyIndexes[battler2] - && (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON) + && (!(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_ACE_POKEMON) || chosenMonId != CalculateEnemyPartyCount() - 1 || CountAIAliveNonEggMonsExcept(PARTY_SIZE) == pokemonInBattle)) { diff --git a/src/battle_debug.c b/src/battle_debug.c index 3fca57e38ffd..1b172046baf9 100644 --- a/src/battle_debug.c +++ b/src/battle_debug.c @@ -1901,8 +1901,8 @@ static void SetUpModifyArrows(struct BattleDebugMenu *data) data->modifyArrows.typeOfVal = VAL_BITFIELD_32; goto CASE_ITEM_STATUS; case LIST_ITEM_AI: - data->modifyArrows.modifiedValPtr = &gBattleResources->ai->aiFlags; - data->modifyArrows.currValue = GetBitfieldValue(gBattleResources->ai->aiFlags, data->bitfield[data->currentSecondaryListItemId].currBit, data->bitfield[data->currentSecondaryListItemId].bitsCount); + data->modifyArrows.modifiedValPtr = &gBattleResources->ai->aiFlags[data->battlerId]; + data->modifyArrows.currValue = GetBitfieldValue(gBattleResources->ai->aiFlags[data->battlerId], data->bitfield[data->currentSecondaryListItemId].currBit, data->bitfield[data->currentSecondaryListItemId].bitsCount); data->modifyArrows.typeOfVal = VAL_BITFIELD_32; goto CASE_ITEM_STATUS; CASE_ITEM_STATUS: diff --git a/src/recorded_battle.c b/src/recorded_battle.c index 1ba62b283712..2ac33a08a0a1 100644 --- a/src/recorded_battle.c +++ b/src/recorded_battle.c @@ -87,7 +87,7 @@ void RecordedBattle_Init(u8 mode) for (j = 0; j < BATTLER_RECORD_SIZE; j++) sBattleRecords[i][j] = 0xFF; sBattleFlags = gBattleTypeFlags; - sAI_Scripts = gBattleResources->ai->aiFlags; + sAI_Scripts = gBattleResources->ai->aiFlags[B_POSITION_OPPONENT_LEFT]; } } }