diff --git a/include/battle_ai_script_commands.h b/include/battle_ai_script_commands.h index eb5d046d8..9f6654a46 100644 --- a/include/battle_ai_script_commands.h +++ b/include/battle_ai_script_commands.h @@ -14,6 +14,7 @@ void BattleAI_SetupItems(void); void BattleAI_SetupFlags(void); void BattleAI_SetupAIData(u8 defaultScoreMoves); u8 BattleAI_ChooseMoveOrAction(void); +bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler); void ClearBattlerMoveHistory(u8 battlerId); void RecordAbilityBattle(u8 battlerId, u8 abilityId); void ClearBattlerAbilityHistory(u8 battlerId); diff --git a/src/battle_ai_script_commands.c b/src/battle_ai_script_commands.c index 68e517f9b..238825227 100644 --- a/src/battle_ai_script_commands.c +++ b/src/battle_ai_script_commands.c @@ -450,12 +450,27 @@ static u32 GetTotalBaseStat(u32 species) + gBaseStats[species].baseSpDefense; } +bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler) +{ + int i; + + for (i = 0; i < MAX_MON_MOVES; i++) + { + u32 move = gBattleResources->battleHistory->usedMoves[opposingBattler].moves[i]; + if (gBattleMoves[move].effect == EFFECT_PROTECT && move != MOVE_ENDURE) + return TRUE; + if (gBattleMoves[move].effect == EFFECT_SEMI_INVULNERABLE && GetWhoStrikesFirst(battlerAI, opposingBattler, TRUE) == 1) + return TRUE; + } + return FALSE; +} + static u8 ChooseMoveOrAction_Singles(void) { u8 currentMoveArray[4]; u8 consideredMoveArray[4]; u8 numOfBestMoves; - s32 i; + s32 i, id; u32 flags = AI_THINKING_STRUCT->aiFlags; RecordLastUsedMoveByTarget(); @@ -478,6 +493,7 @@ static u8 ChooseMoveOrAction_Singles(void) if (AI_THINKING_STRUCT->aiAction & AI_ACTION_WATCH) return AI_CHOICE_WATCH; + gActiveBattler = sBattler_AI; // Consider switching if all moves are worthless to use. if (AI_THINKING_STRUCT->aiFlags & (AI_SCRIPT_CHECK_VIABILITY | AI_SCRIPT_CHECK_BAD_MOVE | AI_SCRIPT_TRY_TO_FAINT | AI_SCRIPT_PREFER_BATON_PASS) && CountUsablePartyMons(sBattler_AI) >= 1 @@ -493,7 +509,6 @@ static u8 ChooseMoveOrAction_Singles(void) break; } - gActiveBattler = sBattler_AI; if (i == MAX_MON_MOVES && GetMostSuitableMonToSwitchInto() != PARTY_SIZE) { AI_THINKING_STRUCT->switchMon = TRUE; @@ -501,6 +516,22 @@ static u8 ChooseMoveOrAction_Singles(void) } } + // Consider switching if your mon with truant is bodied by Protect spam. + // Or is using a double turn semi invulnerable move(such as Fly) and is faster. + if (GetBattlerAbility(sBattler_AI) == ABILITY_TRUANT + && IsTruantMonVulnerable(sBattler_AI, gBattlerTarget) + && gDisableStructs[sBattler_AI].truantCounter + && AI_THINKING_STRUCT->aiFlags & (AI_SCRIPT_CHECK_VIABILITY) + && gBattleMons[sBattler_AI].hp >= gBattleMons[sBattler_AI].maxHP / 2 + && CountUsablePartyMons(sBattler_AI) >= 1) + { + if (GetMostSuitableMonToSwitchInto() != PARTY_SIZE) + { + AI_THINKING_STRUCT->switchMon = TRUE; + return AI_CHOICE_SWITCH; + } + } + numOfBestMoves = 1; currentMoveArray[0] = AI_THINKING_STRUCT->score[0]; consideredMoveArray[0] = 0; diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index ce85a37f7..d0a64088c 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -577,18 +577,143 @@ void AI_TrySwitchOrUseItem(void) BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (gActiveBattler ^ BIT_SIDE) << 8); } +// If there are two(or more) mons to choose from, always choose one that has baton pass +// as most often it can't do much on its own. +static u32 GetBestMonBatonPass(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, int aliveCount) +{ + int i, j, bits = 0; + + for (i = firstId; i < lastId; i++) + { + if (invalidMons & gBitTable[i]) + continue; + + for (j = 0; j < MAX_MON_MOVES; j++) + { + if (GetMonData(&party[i], MON_DATA_MOVE1 + j, NULL) == MOVE_BATON_PASS) + { + bits |= gBitTable[i]; + break; + } + } + } + + if ((aliveCount == 2 || (aliveCount > 2 && Random() % 3 == 0)) && bits) + { + do + { + i = (Random() % (lastId - firstId)) + firstId; + } while (!(bits & gBitTable[i])); + return i; + } + + return PARTY_SIZE; +} + +static u32 GestBestMonOffensive(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, u32 opposingBattler) +{ + int i, bits = 0; + + while (bits != 0x3F) // All mons were checked. + { + int bestDmg = 0; + int bestMonId = PARTY_SIZE; + // Find the mon whose type is the most suitable offensively. + for (i = firstId; i < lastId; i++) + { + if (!(gBitTable[i] & invalidMons) && !(gBitTable[i] & bits)) + { + u16 species = GetMonData(&party[i], MON_DATA_SPECIES); + u32 typeDmg = UQ_4_12(1.0); + + u8 atkType1 = gBaseStats[species].type1; + u8 atkType2 = gBaseStats[species].type2; + u8 defType1 = gBattleMons[opposingBattler].type1; + u8 defType2 = gBattleMons[opposingBattler].type2; + + typeDmg *= GetTypeModifier(atkType1, defType1); + if (atkType2 != atkType1) + typeDmg *= GetTypeModifier(atkType2, defType1); + if (defType2 != defType1) + { + typeDmg *= GetTypeModifier(atkType1, defType2); + if (atkType2 != atkType1) + typeDmg *= GetTypeModifier(atkType2, defType2); + } + if (bestDmg < typeDmg) + { + bestDmg = typeDmg; + bestMonId = i; + } + } + } + + // Ok, we know the mon has the right typing but does it have at least one super effective move? + if (bestMonId != PARTY_SIZE) + { + for (i = 0; i < MAX_MON_MOVES; i++) + { + u32 move = GetMonData(&party[bestMonId], MON_DATA_MOVE1 + i); + if (move != MOVE_NONE && AI_GetTypeEffectiveness(move, gActiveBattler, opposingBattler) >= UQ_4_12(2.0)) + break; + } + + if (i != MAX_MON_MOVES) + return bestMonId; // Has both the typing and at least one super effective move. + + bits |= gBitTable[bestMonId]; // Sorry buddy, we want something better. + } + else + { + bits = 0x3F; // No viable mon to switch. + } + } + + return PARTY_SIZE; +} + +static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, u32 opposingBattler) +{ + int i, j; + int bestDmg = 0; + int bestMonId = PARTY_SIZE; + + gMoveResultFlags = 0; + // If we couldn't find the best mon in terms of typing, find the one that deals most damage. + for (i = firstId; i < lastId; i++) + { + if (gBitTable[i] & invalidMons) + continue; + + for (j = 0; j < MAX_MON_MOVES; j++) + { + u32 move = GetMonData(&party[i], MON_DATA_MOVE1 + j); + if (move != MOVE_NONE && gBattleMoves[move].power != 0) + { + s32 dmg = AI_CalcPartyMonDamage(move, gActiveBattler, opposingBattler, &party[i]); + if (bestDmg < dmg) + { + bestDmg = dmg; + bestMonId = i; + } + } + } + } + + return bestMonId; +} + u8 GetMostSuitableMonToSwitchInto(void) { - u8 opposingBattler = 0; + u32 opposingBattler = 0; u32 bestDmg = 0; - u8 bestMonId = 0; + u32 bestMonId = 0; u8 battlerIn1 = 0, battlerIn2 = 0; s32 firstId = 0; s32 lastId = 0; // + 1 struct Pokemon *party; s32 i, j, aliveCount = 0; - u8 invalidMons = 0, bits = 0; - u16 move; + u8 invalidMons = 0; if (*(gBattleStruct->monToSwitchIntoId + gActiveBattler) != PARTY_SIZE) return *(gBattleStruct->monToSwitchIntoId + gActiveBattler); @@ -629,120 +754,26 @@ u8 GetMostSuitableMonToSwitchInto(void) || gBattlerPartyIndexes[battlerIn1] == i || gBattlerPartyIndexes[battlerIn2] == i || i == *(gBattleStruct->monToSwitchIntoId + battlerIn1) - || i == *(gBattleStruct->monToSwitchIntoId + battlerIn2)) + || i == *(gBattleStruct->monToSwitchIntoId + battlerIn2) + || (GetMonAbility(&party[i]) == ABILITY_TRUANT && IsTruantMonVulnerable(gActiveBattler, opposingBattler))) // While not really invalid per say, not really wise to switch into this mon. invalidMons |= gBitTable[i]; else aliveCount++; } - // If there are two(or more) mons to choose from, always choose one that has baton pass - // as most often it can't do much on its own. - for (i = firstId; i < lastId; i++) - { - if (invalidMons & gBitTable[i]) - continue; - - for (j = 0; j < MAX_MON_MOVES; j++) - { - if (GetMonData(&party[i], MON_DATA_MOVE1 + j, NULL) == MOVE_BATON_PASS) - { - bits |= gBitTable[i]; - break; - } - } - } - if ((aliveCount == 2 || (aliveCount > 2 && Random() % 3 == 0)) && bits) - { - do - { - bestMonId = (Random() % (lastId - firstId)) + firstId; - } while (!(bits & gBitTable[bestMonId])); + bestMonId = GetBestMonBatonPass(party, firstId, lastId, invalidMons, aliveCount); + if (bestMonId != PARTY_SIZE) return bestMonId; - } - bits = 0; - while (bits != 0x3F) // All mons are invalid. - { - bestDmg = 0; - bestMonId = PARTY_SIZE; - // Find the mon whose type is the most suitable offensively. - for (i = firstId; i < lastId; i++) - { - if (!(gBitTable[i] & invalidMons) && !(gBitTable[i] & bits)) - { - u16 species = GetMonData(&party[i], MON_DATA_SPECIES); - u32 typeDmg = UQ_4_12(1.0); + bestMonId = GestBestMonOffensive(party, firstId, lastId, invalidMons, opposingBattler); + if (bestMonId != PARTY_SIZE) + return bestMonId; - u8 atkType1 = gBaseStats[species].type1; - u8 atkType2 = gBaseStats[species].type2; - u8 defType1 = gBattleMons[opposingBattler].type1; - u8 defType2 = gBattleMons[opposingBattler].type2; + bestMonId = GetBestMonDmg(party, firstId, lastId, invalidMons, opposingBattler); + if (bestMonId != PARTY_SIZE) + return bestMonId; - typeDmg *= GetTypeModifier(atkType1, defType1); - if (atkType2 != atkType1) - typeDmg *= GetTypeModifier(atkType2, defType1); - if (defType2 != defType1) - { - typeDmg *= GetTypeModifier(atkType1, defType2); - if (atkType2 != atkType1) - typeDmg *= GetTypeModifier(atkType2, defType2); - } - if (bestDmg < typeDmg) - { - bestDmg = typeDmg; - bestMonId = i; - } - } - } - - // Ok, we know the mon has the right typing but does it have at least one super effective move? - if (bestMonId != PARTY_SIZE) - { - for (i = 0; i < MAX_MON_MOVES; i++) - { - move = GetMonData(&party[bestMonId], MON_DATA_MOVE1 + i); - if (move != MOVE_NONE && AI_GetTypeEffectiveness(move, gActiveBattler, opposingBattler) >= UQ_4_12(2.0)) - break; - } - - if (i != MAX_MON_MOVES) - return bestMonId; // Has both the typing and at least one super effective move. - - bits |= gBitTable[bestMonId]; // Sorry buddy, we want something better. - } - else - { - bits = 0x3F; // No viable mon to switch. - } - } - - gBattleStruct->dynamicMoveType = 0; - gMoveResultFlags = 0; - bestDmg = 0; - bestMonId = 6; - - // If we couldn't find the best mon in terms of typing, find the one that deals most damage. - for (i = firstId; i < lastId; i++) - { - if (gBitTable[i] & invalidMons) - continue; - - for (j = 0; j < MAX_MON_MOVES; j++) - { - move = GetMonData(&party[i], MON_DATA_MOVE1 + j); - if (move != MOVE_NONE && gBattleMoves[move].power != 0) - { - s32 dmg = AI_CalcPartyMonDamage(move, gActiveBattler, opposingBattler, &party[i]); - if (bestDmg < dmg) - { - bestDmg = dmg; - bestMonId = i; - } - } - } - } - - return bestMonId; + return PARTY_SIZE; } static u8 GetAI_ItemType(u16 itemId, const u8 *itemEffect)