From cc4a9b9f4b5ea742cccab1dc5d889d6a9956a7ed Mon Sep 17 00:00:00 2001 From: Ct11217 Date: Thu, 15 Sep 2022 22:57:29 -0600 Subject: [PATCH] Recommit to Dev branch --- include/battle_ai_switch_items.h | 2 +- src/battle_ai_main.c | 4 +- src/battle_ai_switch_items.c | 168 +++++++++++++++---------- src/battle_controller_opponent.c | 5 +- src/battle_controller_player_partner.c | 2 +- 5 files changed, 109 insertions(+), 72 deletions(-) diff --git a/include/battle_ai_switch_items.h b/include/battle_ai_switch_items.h index 70dc41b34..77d1e6315 100644 --- a/include/battle_ai_switch_items.h +++ b/include/battle_ai_switch_items.h @@ -33,7 +33,7 @@ enum { void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId); void AI_TrySwitchOrUseItem(void); -u8 GetMostSuitableMonToSwitchInto(void); +u8 GetMostSuitableMonToSwitchInto(bool8); bool32 ShouldSwitch(void); #endif // GUARD_BATTLE_AI_SWITCH_ITEMS_H diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 312ff853c..7bc4edae6 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -356,7 +356,7 @@ static u8 ChooseMoveOrAction_Singles(void) break; } - if (i == MAX_MON_MOVES && GetMostSuitableMonToSwitchInto() != PARTY_SIZE) + if (i == MAX_MON_MOVES && GetMostSuitableMonToSwitchInto(TRUE) != PARTY_SIZE) { AI_THINKING_STRUCT->switchMon = TRUE; return AI_CHOICE_SWITCH; @@ -370,7 +370,7 @@ static u8 ChooseMoveOrAction_Singles(void) && gDisableStructs[sBattler_AI].truantCounter && gBattleMons[sBattler_AI].hp >= gBattleMons[sBattler_AI].maxHP / 2) { - if (GetMostSuitableMonToSwitchInto() != PARTY_SIZE) + if (GetMostSuitableMonToSwitchInto(TRUE) != PARTY_SIZE) { AI_THINKING_STRUCT->switchMon = TRUE; return AI_CHOICE_SWITCH; diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 4948113a2..f8e60ee7e 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -26,6 +26,7 @@ static bool8 ShouldUseItem(void); static bool32 AiExpectsToFaintPlayer(void); static bool32 AI_ShouldHeal(u32 healAmount); static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount); +static bool32 AI_CheckSurvivabilty(bool8 checkSurvivability, int playerPokemon, int aiPokemon); void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId) { @@ -414,12 +415,12 @@ static bool8 ShouldSwitchIfAbilityBenefit(void) moduloChance = 4; //25% //Attempt to cure bad ailment if (gBattleMons[gActiveBattler].status1 & (STATUS1_SLEEP | STATUS1_FREEZE | STATUS1_TOXIC_POISON) - && GetMostSuitableMonToSwitchInto() != PARTY_SIZE) + && GetMostSuitableMonToSwitchInto(TRUE) != PARTY_SIZE) break; //Attempt to cure lesser ailment if ((gBattleMons[gActiveBattler].status1 & STATUS1_ANY) && (gBattleMons[gActiveBattler].hp >= gBattleMons[gActiveBattler].maxHP / 2) - && GetMostSuitableMonToSwitchInto() != PARTY_SIZE + && GetMostSuitableMonToSwitchInto(TRUE) != PARTY_SIZE && Random() % (moduloChance*chanceReducer) == 0) break; @@ -431,7 +432,7 @@ static bool8 ShouldSwitchIfAbilityBenefit(void) if (gBattleMons[gActiveBattler].status1 & STATUS1_ANY) return FALSE; if ((gBattleMons[gActiveBattler].hp <= ((gBattleMons[gActiveBattler].maxHP * 2) / 3)) - && GetMostSuitableMonToSwitchInto() != PARTY_SIZE + && GetMostSuitableMonToSwitchInto(TRUE) != PARTY_SIZE && Random() % (moduloChance*chanceReducer) == 0) break; @@ -726,7 +727,7 @@ void AI_TrySwitchOrUseItem(void) { if (*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) == PARTY_SIZE) { - s32 monToSwitchId = GetMostSuitableMonToSwitchInto(); + s32 monToSwitchId = GetMostSuitableMonToSwitchInto(TRUE); if (monToSwitchId == PARTY_SIZE) { if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) @@ -746,6 +747,8 @@ void AI_TrySwitchOrUseItem(void) { if (GetMonData(&party[monToSwitchId], MON_DATA_HP) == 0) continue; + if (GetMonData(&party[monToSwitchId], MON_DATA_SPECIES2) == SPECIES_NONE) + continue; if (monToSwitchId == gBattlerPartyIndexes[battlerIn1]) continue; if (monToSwitchId == gBattlerPartyIndexes[battlerIn2]) @@ -779,7 +782,7 @@ void AI_TrySwitchOrUseItem(void) // 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) +static u32 GetBestMonBatonPass(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, int aliveCount, bool8 checkSurvivability) { int i, j, bits = 0; @@ -788,6 +791,9 @@ static u32 GetBestMonBatonPass(struct Pokemon *party, int firstId, int lastId, u if (invalidMons & gBitTable[i]) continue; + if (AI_CheckSurvivabilty(checkSurvivability, BATTLE_OPPOSITE(gActiveBattler), i)) + continue; + for (j = 0; j < MAX_MON_MOVES; j++) { if (GetMonData(&party[i], MON_DATA_MOVE1 + j, NULL) == MOVE_BATON_PASS) @@ -810,69 +816,71 @@ static u32 GetBestMonBatonPass(struct Pokemon *party, int firstId, int lastId, u return PARTY_SIZE; } -static u32 GetBestMonTypeMatchup(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, u32 opposingBattler) +static u32 GetBestMonTypeMatchup(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, u32 opposingBattler, bool8 checkSurvivability) { - int i, bits = 0; - - while (bits != 0x3F) // All mons were checked. + int i, j = 0; + u32 bestResist = UQ_4_12(1.0); + int bestMonId = PARTY_SIZE; + // Find the mon whose type is the most suitable defensively. + for (i = firstId; i < lastId; i++) { - u32 bestResist = UQ_4_12(1.0); - int bestMonId = PARTY_SIZE; - // Find the mon whose type is the most suitable defensively. - for (i = firstId; i < lastId; i++) + u16 species = GetMonData(&party[i], MON_DATA_SPECIES); + u32 typeEffectiveness = UQ_4_12(1.0); + + u8 atkType1 = gBattleMons[opposingBattler].type1; + u8 atkType2 = gBattleMons[opposingBattler].type2; + u8 defType1 = gBaseStats[species].type1; + u8 defType2 = gBaseStats[species].type2; + + if (gBitTable[i] & invalidMons) + continue; + if (AI_CheckSurvivabilty(checkSurvivability, BATTLE_OPPOSITE(gActiveBattler), i)) + continue; + + typeEffectiveness *= GetTypeModifier(atkType1, defType1); + if (atkType2 != atkType1) + typeEffectiveness *= GetTypeModifier(atkType2, defType1); + if (defType2 != defType1) { - if (!(gBitTable[i] & invalidMons) && !(gBitTable[i] & bits)) - { - u16 species = GetMonData(&party[i], MON_DATA_SPECIES); - u32 typeEffectiveness = UQ_4_12(1.0); - - u8 atkType1 = gBattleMons[opposingBattler].type1; - u8 atkType2 = gBattleMons[opposingBattler].type2; - u8 defType1 = gBaseStats[species].type1; - u8 defType2 = gBaseStats[species].type2; - - typeEffectiveness *= GetTypeModifier(atkType1, defType1); - if (atkType2 != atkType1) - typeEffectiveness *= GetTypeModifier(atkType2, defType1); - if (defType2 != defType1) - { - typeEffectiveness *= GetTypeModifier(atkType1, defType2); - if (atkType2 != atkType1) - typeEffectiveness *= GetTypeModifier(atkType2, defType2); - } - if (typeEffectiveness < bestResist) - { - bestResist = typeEffectiveness; - bestMonId = i; - } - } + typeEffectiveness *= GetTypeModifier(atkType1, defType2); + if (atkType2 != atkType1) + typeEffectiveness *= GetTypeModifier(atkType2, defType2); } - - // Ok, we know the mon has the right typing but does it have at least one super effective move? - if (bestMonId != PARTY_SIZE) + if ((typeEffectiveness < bestResist) + || ((typeEffectiveness <= bestResist) && !checkSurvivability)) //Fine with a nuetral matchup on second time through { - 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. + bestResist = typeEffectiveness; + bestMonId = i; } } - return PARTY_SIZE; + // Ok, we don't have anything that type resists. But do we at least have something with a super effective move? + if (bestMonId == PARTY_SIZE) + { + // Find the mon that has an attack most suited offensively + for (i = firstId; i < lastId; i++) + { + if (gBitTable[i] & invalidMons) + continue; + if (AI_CheckSurvivabilty(checkSurvivability, BATTLE_OPPOSITE(gActiveBattler), i)) + continue; + + for (j = 0; j < MAX_MON_MOVES; j++) + { + u32 move = GetMonData(&party[i], MON_DATA_MOVE1 + j); + if (move != MOVE_NONE && AI_GetTypeEffectiveness(move, gActiveBattler, opposingBattler) >= UQ_4_12(2.0)) + { + bestMonId = i; // Has at least one super effective move. + break; + } + } + } + } + + return bestMonId; } -static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, u32 opposingBattler) +static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, u32 opposingBattler, bool8 checkSurvivability) { int i, j; int bestDmg = 0; @@ -884,6 +892,8 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva { if (gBitTable[i] & invalidMons) continue; + if (AI_CheckSurvivabilty(checkSurvivability, BATTLE_OPPOSITE(gActiveBattler), i)) + continue; for (j = 0; j < MAX_MON_MOVES; j++) { @@ -903,10 +913,10 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva return bestMonId; } -u8 GetMostSuitableMonToSwitchInto(void) +u8 GetMostSuitableMonToSwitchInto(bool8 checkSurvivability) { u32 opposingBattler = 0; - u32 bestMonId = 0; + u32 bestMonId = PARTY_SIZE; u8 battlerIn1 = 0, battlerIn2 = 0; s32 firstId = 0; s32 lastId = 0; // + 1 @@ -948,7 +958,7 @@ u8 GetMostSuitableMonToSwitchInto(void) // Get invalid slots ids. for (i = firstId; i < lastId; i++) { - if (GetMonData(&party[i], MON_DATA_SPECIES) == SPECIES_NONE + if (GetMonData(&party[i], MON_DATA_SPECIES2) == SPECIES_NONE || GetMonData(&party[i], MON_DATA_HP) == 0 || gBattlerPartyIndexes[battlerIn1] == i || gBattlerPartyIndexes[battlerIn2] == i @@ -962,19 +972,26 @@ u8 GetMostSuitableMonToSwitchInto(void) aliveCount++; } - bestMonId = GetBestMonBatonPass(party, firstId, lastId, invalidMons, aliveCount); + bestMonId = GetBestMonBatonPass(party, firstId, lastId, invalidMons, aliveCount, checkSurvivability); if (bestMonId != PARTY_SIZE) return bestMonId; - bestMonId = GetBestMonTypeMatchup(party, firstId, lastId, invalidMons, opposingBattler); + bestMonId = GetBestMonTypeMatchup(party, firstId, lastId, invalidMons, opposingBattler, checkSurvivability); if (bestMonId != PARTY_SIZE) return bestMonId; - bestMonId = GetBestMonDmg(party, firstId, lastId, invalidMons, opposingBattler); + bestMonId = GetBestMonDmg(party, firstId, lastId, invalidMons, opposingBattler, checkSurvivability); if (bestMonId != PARTY_SIZE) return bestMonId; - return PARTY_SIZE; + //Didn't find any good options first time around. Try again without checking survivabilty, better than a random mon + if (checkSurvivability && bestMonId == PARTY_SIZE) + { + bestMonId = GetMostSuitableMonToSwitchInto(FALSE); + return bestMonId; + } + + return bestMonId; } static u8 GetAI_ItemType(u16 itemId, const u8 *itemEffect) @@ -1178,3 +1195,24 @@ static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount) } return FALSE; } + +static bool32 AI_CheckSurvivabilty(bool8 checkSurvivability, int playerPokemon, int aiPokemon) +{ + if (!checkSurvivability) + return FALSE; + + if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING) + { + //Opponent can OHKO AI, don't send this Pokemon out + if (CanTargetFaintAiWithMod(playerPokemon, aiPokemon, 0, 0)) + return TRUE; + + //Opponent can 2HKO AI and AI cannot strike first + //ToDo: Modify for switches when AI has already attacked (Volt Switch etc.) + if (CanTargetFaintAiWithMod(playerPokemon, aiPokemon, 0, 2) + && GetWhoStrikesFirst(playerPokemon, aiPokemon, TRUE) == 0) //Player strikes first + return TRUE; + } + + return FALSE; +} \ No newline at end of file diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c index c32d0cd58..8e95e3bb9 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -1676,8 +1676,7 @@ static void OpponentHandleChoosePokemon(void) if (*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) == PARTY_SIZE) { - chosenMonId = GetMostSuitableMonToSwitchInto(); - + chosenMonId = GetMostSuitableMonToSwitchInto(TRUE); if (chosenMonId == PARTY_SIZE) { s32 battler1, battler2, firstId, lastId; @@ -1691,7 +1690,6 @@ static void OpponentHandleChoosePokemon(void) battler1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); battler2 = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); pokemonInBattle = 2; - } GetAIPartyIndexes(gActiveBattler, &firstId, &lastId); @@ -1699,6 +1697,7 @@ static void OpponentHandleChoosePokemon(void) for (chosenMonId = (lastId-1); chosenMonId >= firstId; chosenMonId--) { if (GetMonData(&gEnemyParty[chosenMonId], MON_DATA_HP) != 0 + && GetMonData(&gEnemyParty[chosenMonId], MON_DATA_SPECIES2) != SPECIES_NONE && chosenMonId != gBattlerPartyIndexes[battler1] && chosenMonId != gBattlerPartyIndexes[battler2] && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON diff --git a/src/battle_controller_player_partner.c b/src/battle_controller_player_partner.c index d39d745a6..8af44377d 100644 --- a/src/battle_controller_player_partner.c +++ b/src/battle_controller_player_partner.c @@ -1548,7 +1548,7 @@ static void PlayerPartnerHandleChooseItem(void) static void PlayerPartnerHandleChoosePokemon(void) { - s32 chosenMonId = GetMostSuitableMonToSwitchInto(); + s32 chosenMonId = GetMostSuitableMonToSwitchInto(TRUE); if (chosenMonId == 6) // just switch to the next mon {