diff --git a/docs/tutorials/ai_flags.md b/docs/tutorials/ai_flags.md index b373a3f078..5d37af13a5 100644 --- a/docs/tutorials/ai_flags.md +++ b/docs/tutorials/ai_flags.md @@ -145,7 +145,7 @@ Marks the last Pokemon in the party as the Ace Pokemon. It will not be used unle Marks the last two Pokémon in the party as Ace Pokémon, with the same behaviour as `AI_FLAG_ACE_POKEMON`. Intented for double battles where you battle one trainer id that represents two trainers, ie Twins, Couples. If you apply this flag to trainers outside of double battles or in cases where two trainers can challenge you at the same time, it has the same behaviour. For example vs two trainers with `AI_FLAG_DOUBLE_ACE_POKEMON` there will be a total of 4 Ace Pokémon. ## `AI_FLAG_OMNISCIENT` -AI has full knowledge of player moves, abilities, and hold items, and can use this knowledge when making decisions. +AI has full knowledge of player moves, abilities, and hold items, and can use this knowledge when making decisions. More modular omniscience settings are included in the flags `AI_FLAG_ABILITY_OMNISCIENCE`, `AI_FLAG_ITEM_OMNISCIENCE`, and `AI_FLAG_MOVE_OMNISCIENCE `. ## `AI_FLAG_KNOW_OPPONENT_PARTY` AI has full knowledge of the species in the player's party, as well as their fainted status; no other omniscient knowledge is included. Functions similarly to a team preview. diff --git a/include/constants/battle_ai.h b/include/constants/battle_ai.h index cc2d68cc8c..77a7542c1b 100644 --- a/include/constants/battle_ai.h +++ b/include/constants/battle_ai.h @@ -41,6 +41,9 @@ #define AI_FLAG_KNOW_OPPONENT_PARTY AI_FLAG(31) // AI knows all the species in the player's party, but not moves/items/abilities unless they've been seen. #define AI_FLAG_RANDOMIZE_SWITCHIN AI_FLAG(32) // AI will randomly choose between eligible switchin candidates of a given category instead of picking the last one in the party. #define AI_FLAG_RANDOMIZE_PARTY_INDICES AI_FLAG(33) // AI will randomize the order of the mons in its party, including the lead. Not an AI flag really, just a way to trigger TPP functionality +#define AI_FLAG_ABILITY_OMNISCIENCE AI_FLAG(34) // AI knows the player's ability +#define AI_FLAG_ITEM_OMNISCIENCE AI_FLAG(35) // AI knows the player's item +#define AI_FLAG_MOVE_OMNISCIENCE AI_FLAG(36) // AI knows the player's moves // The following options are enough to have a basic/smart trainer. Any other addtion could make the trainer worse/better depending on the flag #define AI_FLAG_BASIC_TRAINER (AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 26b92de8f7..2ff6cb7a86 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -107,12 +107,12 @@ static s32 (*const sBattleAiFuncTable[])(enum BattlerId, enum BattlerId, enum Mo [28] = NULL, // AI_FLAG_ASSUME_STAB [29] = NULL, // AI_FLAG_ASSUME_STATUS_MOVES [30] = AI_AttacksPartner, // AI_FLAG_ATTACKS_PARTNER - [31] = NULL, // Unused - [32] = NULL, // Unused - [33] = NULL, // Unused - [34] = NULL, // Unused - [35] = NULL, // Unused - [36] = NULL, // Unused + [31] = NULL, // AI_FLAG_KNOW_OPPONENT_PARTY + [32] = NULL, // AI_FLAG_RANDOMIZE_SWITCHIN + [33] = NULL, // AI_FLAG_RANDOMIZE_PARTY_INDICES + [34] = NULL, // AI_FLAG_ABILITY_OMNISCIENCE + [35] = NULL, // AI_FLAG_ITEM_OMNISCIENCE + [36] = NULL, // AI_FLAG_MOVE_OMNISCIENCE [37] = NULL, // Unused [38] = NULL, // Unused [39] = NULL, // Unused @@ -548,6 +548,9 @@ static void CopyBattlerDataToAIParty(u32 bPosition, enum BattleTrainer trainer) void Ai_InitPartyStruct(void) { bool32 isOmniscient = (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_OMNISCIENT) || (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_OMNISCIENT); + bool32 isAbilityOmniscient = (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_ABILITY_OMNISCIENCE) || (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_ABILITY_OMNISCIENCE); + bool32 isItemOmniscient = (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_ITEM_OMNISCIENCE) || (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_ITEM_OMNISCIENCE); + bool32 isMoveOmniscient = (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_MOVE_OMNISCIENCE) || (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_MOVE_OMNISCIENCE); bool32 hasPartyKnowledge = (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_KNOW_OPPONENT_PARTY) || (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_KNOW_OPPONENT_PARTY); struct Pokemon *mon; @@ -583,11 +586,17 @@ void Ai_InitPartyStruct(void) if (isOmniscient || hasPartyKnowledge) gAiPartyData->mons[trainer][monIndex].species = GetMonData(mon, MON_DATA_SPECIES); - if (isOmniscient) + if (isOmniscient || isAbilityOmniscient) + gAiPartyData->mons[trainer][monIndex].ability = GetMonAbility(mon); + + if (isOmniscient || isItemOmniscient) { gAiPartyData->mons[trainer][monIndex].item = GetMonData(mon, MON_DATA_HELD_ITEM); gAiPartyData->mons[trainer][monIndex].heldEffect = GetItemHoldEffect(gAiPartyData->mons[trainer][monIndex].item); - gAiPartyData->mons[trainer][monIndex].ability = GetMonAbility(mon); + } + + if (isOmniscient || isMoveOmniscient) + { for (u32 moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) gAiPartyData->mons[trainer][monIndex].moves[moveIndex] = GetMonData(mon, MON_DATA_MOVE1 + moveIndex); } diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 8b2ea80253..6d642f2161 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -1763,7 +1763,7 @@ enum Ability AI_DecideKnownAbilityForTurn(enum BattlerId battlerId) return gBattleMons[battlerId].volatiles.overwrittenAbility; // The AI knows its own ability, and omniscience handling - if (IsAiBattlerAware(battlerId) || (IsAiBattlerAssumingStab(battlerId) && ASSUME_STAB_SEES_ABILITY)) + if (IsAiBattlerAware(battlerId) || (IsAiBattlerAssumingStab(battlerId) && ASSUME_STAB_SEES_ABILITY) || IsAiFlagPresent(AI_FLAG_ABILITY_OMNISCIENCE)) return knownAbility; // Check neutralizing gas, gastro acid @@ -1803,7 +1803,7 @@ enum HoldEffect AI_DecideHoldEffectForTurn(enum BattlerId battlerId) if (gBattleMons[battlerId].item == ITEM_NONE) // Failsafe for when user recorded an item but it was consumed return holdEffect; - if (!IsAiBattlerAware(battlerId)) + if (!IsAiBattlerAware(battlerId) && !IsAiFlagPresent(AI_FLAG_ITEM_OMNISCIENCE)) holdEffect = gAiPartyData->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].heldEffect; else holdEffect = GetBattlerHoldEffectIgnoreNegation(battlerId); @@ -2486,7 +2486,7 @@ bool32 CanIndexMoveFaintTarget(enum BattlerId battlerAtk, enum BattlerId battler enum Move *GetMovesArray(enum BattlerId battler) { - if (IsAiBattlerAware(battler) || IsAiBattlerAware(BATTLE_PARTNER(battler))) + if (IsAiBattlerAware(battler) || IsAiBattlerAware(BATTLE_PARTNER(battler)) || IsAiFlagPresent(AI_FLAG_MOVE_OMNISCIENCE)) return gBattleMons[battler].moves; else return gBattleHistory->usedMoves[battler]; diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index 8b783b2985..2cd4c61096 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -1445,3 +1445,61 @@ AI_MULTI_BATTLE_TEST("AI does not target itself with selected moves in doubles ( NOT HP_BAR(opponentRight); } } + +AI_SINGLE_BATTLE_TEST("AI_FLAG_ABILITY_OMNISCIENCE: AI knows the player's ability") +{ + u64 aiFlags; + u32 passChance; + PARAMETRIZE { passChance = 1; aiFlags = 0; } + PARAMETRIZE { passChance = 3; aiFlags = AI_FLAG_ABILITY_OMNISCIENCE; } + PASSES_RANDOMLY(passChance, 3, RNG_AI_ABILITY); + GIVEN { + ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiFlags); + PLAYER(SPECIES_BRONZONG) { Ability(ABILITY_LEVITATE); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_SANDSLASH) { Moves(MOVE_EARTHQUAKE, MOVE_TACKLE); } + } WHEN { + TURN { + EXPECT_MOVE(opponent, MOVE_TACKLE); + } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_ITEM_OMNISCIENCE: AI knows the player's item") +{ + u64 aiFlags; + PARAMETRIZE { aiFlags = 0; } + PARAMETRIZE { aiFlags = AI_FLAG_ITEM_OMNISCIENCE; } + GIVEN { + ASSUME(IsPowderMove(MOVE_SPORE) == TRUE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiFlags); + PLAYER(SPECIES_GULPIN) { Item(ITEM_SAFETY_GOGGLES); } + OPPONENT(SPECIES_BRELOOM) { Moves(MOVE_SPORE, MOVE_TACKLE); } + } WHEN { + if (aiFlags == AI_FLAG_ITEM_OMNISCIENCE) + TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); } + else + TURN { EXPECT_MOVE(opponent, MOVE_SPORE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_MOVE_OMNISCIENCE: AI knows the player's moves") +{ + u64 aiFlag = 0; + PARAMETRIZE { aiFlag = 0; } + PARAMETRIZE { aiFlag = AI_FLAG_MOVE_OMNISCIENCE; } + GIVEN { + ASSUME(GetMoveType(MOVE_FLAMETHROWER) == TYPE_FIRE); + ASSUME(IsSpeciesOfType(SPECIES_TYPHLOSION, TYPE_FIRE)); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | aiFlag); + PLAYER(SPECIES_TYPHLOSION) { Speed(5); Moves(MOVE_TACKLE, MOVE_FLAMETHROWER); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); Moves(MOVE_TACKLE); Level(1); } + OPPONENT(SPECIES_SCIZOR) { Speed(4); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_BLISSEY) { Speed(4); Moves(MOVE_TACKLE); } + } WHEN { + if (aiFlag == AI_FLAG_MOVE_OMNISCIENCE) + TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_TACKLE); EXPECT_SEND_OUT(opponent, 2); } + else + TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_TACKLE); EXPECT_SEND_OUT(opponent, 1); } + } +}