Skip to content

Commit

Permalink
Improve AI switching, so it doesn't get killed on switch-in (#3146)
Browse files Browse the repository at this point in the history
  • Loading branch information
ghoulslash authored Aug 4, 2023
2 parents 4ae6ae4 + e7bc42f commit a0dd77d
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 65 deletions.
4 changes: 3 additions & 1 deletion include/battle_ai_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,9 @@ bool32 PartnerMoveIsSameNoTarget(u8 battlerAtkPartner, u16 move, u16 partnerMove
bool32 ShouldUseWishAromatherapy(u8 battlerAtk, u8 battlerDef, u16 move);

// party logic
s32 AI_CalcPartyMonDamage(u16 move, u8 battlerAtk, u8 battlerDef, struct Pokemon *mon);
struct BattlePokemon *AllocSaveBattleMons(void);
void FreeRestoreBattleMons(struct BattlePokemon *savedBattleMons);
s32 AI_CalcPartyMonBestMoveDamage(u32 battlerAtk, u32 battlerDef, struct Pokemon *attackerMon, struct Pokemon *targetMon);
s32 CountUsablePartyMons(u8 battlerId);
bool32 IsPartyFullyHealedExceptBattler(u8 battler);
bool32 PartyHasMoveSplit(u8 battlerId, u8 split);
Expand Down
101 changes: 52 additions & 49 deletions src/battle_ai_switch_items.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ static bool8 ShouldUseItem(void);
static bool32 AiExpectsToFaintPlayer(void);
static bool32 AI_ShouldHeal(u32 healAmount);
static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount);
static bool32 IsAiPartyMonOHKOBy(u32 battlerAtk, struct Pokemon *aiMon);

static bool32 IsAceMon(u32 battlerId, u32 monPartyId)
{
Expand Down Expand Up @@ -112,11 +113,7 @@ static bool8 ShouldSwitchIfWonderGuard(void)
// Find a Pokemon in the party that has a super effective move.
for (i = firstId; i < lastId; i++)
{
if (GetMonData(&party[i], MON_DATA_HP) == 0)
continue;
if (GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE)
continue;
if (GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG)
if (!IsValidForBattle(&party[i]))
continue;
if (i == gBattlerPartyIndexes[gActiveBattler])
continue;
Expand Down Expand Up @@ -195,13 +192,9 @@ static bool8 FindMonThatAbsorbsOpponentsMove(void)

for (i = firstId; i < lastId; i++)
{
u16 species;
u16 monAbility;

if (GetMonData(&party[i], MON_DATA_HP) == 0)
continue;
species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
if (species == SPECIES_NONE || species == SPECIES_EGG)
if (!IsValidForBattle(&party[i]))
continue;
if (i == gBattlerPartyIndexes[battlerIn1])
continue;
Expand All @@ -215,7 +208,6 @@ static bool8 FindMonThatAbsorbsOpponentsMove(void)
continue;

monAbility = GetMonAbility(&party[i]);

if (absorbingTypeAbility == monAbility && Random() & 1)
{
// we found a mon.
Expand Down Expand Up @@ -290,9 +282,7 @@ static bool8 ShouldSwitchIfGameStatePrompt(void)
continue;

//Look for mon in party that is able to be switched into and has ability that sets terrain
if (GetMonData(&party[i], MON_DATA_HP) != 0
&& GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE
&& GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG
if (IsValidForBattle(&party[i])
&& i != gBattlerPartyIndexes[gActiveBattler]
&& i != gBattlerPartyIndexes[BATTLE_PARTNER(gActiveBattler)]
&& IsBattlerGrounded(gActiveBattler)
Expand Down Expand Up @@ -561,13 +551,9 @@ static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent)

for (i = firstId; i < lastId; i++)
{
u16 species;
u16 monAbility;
u16 species, monAbility;

if (GetMonData(&party[i], MON_DATA_HP) == 0)
continue;
species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
if (species == SPECIES_NONE || species == SPECIES_EGG)
if (!IsValidForBattle(&party[i]))
continue;
if (i == gBattlerPartyIndexes[battlerIn1])
continue;
Expand All @@ -580,8 +566,8 @@ static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent)
if (IsAceMon(gActiveBattler, i))
continue;

species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
monAbility = GetMonAbility(&party[i]);

CalcPartyMonTypeEffectivenessMultiplier(gLastLandedMoves[gActiveBattler], species, monAbility);
if (gMoveResultFlags & flags)
{
Expand Down Expand Up @@ -650,11 +636,7 @@ bool32 ShouldSwitch(void)

for (i = firstId; i < lastId; i++)
{
if (GetMonData(&party[i], MON_DATA_HP) == 0)
continue;
if (GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE)
continue;
if (GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG)
if (!IsValidForBattle(&party[i]))
continue;
if (i == gBattlerPartyIndexes[battlerIn1])
continue;
Expand Down Expand Up @@ -751,7 +733,7 @@ void AI_TrySwitchOrUseItem(void)

for (monToSwitchId = (lastId-1); monToSwitchId >= firstId; monToSwitchId--)
{
if (GetMonData(&party[monToSwitchId], MON_DATA_HP) == 0)
if (!IsValidForBattle(&party[monToSwitchId]))
continue;
if (monToSwitchId == gBattlerPartyIndexes[battlerIn1])
continue;
Expand Down Expand Up @@ -785,14 +767,16 @@ 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, u32 opposingBattler)
{
int i, j, bits = 0;

for (i = firstId; i < lastId; i++)
{
if (invalidMons & gBitTable[i])
continue;
if (IsAiPartyMonOHKOBy(opposingBattler, &party[i]))
continue;

for (j = 0; j < MAX_MON_MOVES; j++)
{
Expand Down Expand Up @@ -837,6 +821,9 @@ static u32 GetBestMonTypeMatchup(struct Pokemon *party, int firstId, int lastId,
u8 defType1 = gSpeciesInfo[species].types[0];
u8 defType2 = gSpeciesInfo[species].types[1];

if (IsAiPartyMonOHKOBy(opposingBattler, &party[i]))
continue;

typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType1)));
if (atkType2 != atkType1)
typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType1)));
Expand Down Expand Up @@ -881,7 +868,7 @@ static u32 GetBestMonTypeMatchup(struct Pokemon *party, int firstId, int lastId,
static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, u32 opposingBattler)
{
int i, j;
int bestDmg = 0;
int dmg, bestDmg = 0;
int bestMonId = PARTY_SIZE;

gMoveResultFlags = 0;
Expand All @@ -890,19 +877,14 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva
{
if (gBitTable[i] & invalidMons)
continue;
if (IsAiPartyMonOHKOBy(opposingBattler, &party[i]))
continue;

for (j = 0; j < MAX_MON_MOVES; j++)
dmg = AI_CalcPartyMonBestMoveDamage(gActiveBattler, opposingBattler, &party[i], NULL);
if (bestDmg < dmg)
{
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;
}
}
bestDmg = dmg;
bestMonId = i;
}
}

Expand All @@ -912,7 +894,7 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva
u8 GetMostSuitableMonToSwitchInto(void)
{
u32 opposingBattler = 0;
u32 bestMonId = 0;
u32 bestMonId = PARTY_SIZE;
u8 battlerIn1 = 0, battlerIn2 = 0;
s32 firstId = 0;
s32 lastId = 0; // + 1
Expand Down Expand Up @@ -954,10 +936,7 @@ u8 GetMostSuitableMonToSwitchInto(void)
// Get invalid slots ids.
for (i = firstId; i < lastId; i++)
{
u16 species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
if (species == SPECIES_NONE
|| species == SPECIES_EGG
|| GetMonData(&party[i], MON_DATA_HP) == 0
if (!IsValidForBattle(&party[i])
|| gBattlerPartyIndexes[battlerIn1] == i
|| gBattlerPartyIndexes[battlerIn2] == i
|| i == *(gBattleStruct->monToSwitchIntoId + battlerIn1)
Expand All @@ -977,7 +956,7 @@ u8 GetMostSuitableMonToSwitchInto(void)
}
}

bestMonId = GetBestMonBatonPass(party, firstId, lastId, invalidMons, aliveCount);
bestMonId = GetBestMonBatonPass(party, firstId, lastId, invalidMons, aliveCount, opposingBattler);
if (bestMonId != PARTY_SIZE)
return bestMonId;

Expand Down Expand Up @@ -1040,9 +1019,7 @@ static bool8 ShouldUseItem(void)

for (i = 0; i < PARTY_SIZE; i++)
{
if (GetMonData(&party[i], MON_DATA_HP) != 0
&& GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE
&& GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG)
if (IsValidForBattle(&party[i]))
{
validMons++;
}
Expand Down Expand Up @@ -1155,3 +1132,29 @@ static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount)
}
return FALSE;
}

static bool32 IsAiPartyMonOHKOBy(u32 battlerAtk, struct Pokemon *aiMon)
{
bool32 ret = FALSE;
struct BattlePokemon *savedBattleMons;
s32 hp = GetMonData(aiMon, MON_DATA_HP);
s32 bestDmg = AI_CalcPartyMonBestMoveDamage(battlerAtk, gActiveBattler, NULL, aiMon);

switch (GetNoOfHitsToKO(bestDmg, hp))
{
case 1:
ret = TRUE;
break;
case 2: // if AI mon is faster allow 2 turns
savedBattleMons = AllocSaveBattleMons();
PokemonToBattleMon(aiMon, &gBattleMons[gActiveBattler]);
if (AI_WhoStrikesFirst(gActiveBattler, battlerAtk, 0) == AI_IS_SLOWER)
ret = TRUE;
else
ret = FALSE;
FreeRestoreBattleMons(savedBattleMons);
break;
}

return ret;
}
48 changes: 36 additions & 12 deletions src/battle_ai_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -3369,25 +3369,49 @@ bool32 ShouldUseWishAromatherapy(u8 battlerAtk, u8 battlerDef, u16 move)
return FALSE;
}

#define SIZE_G_BATTLE_MONS (sizeof(struct BattlePokemon) * MAX_BATTLERS_COUNT)

struct BattlePokemon *AllocSaveBattleMons(void)
{
struct BattlePokemon *savedBattleMons = Alloc(SIZE_G_BATTLE_MONS);
memcpy(savedBattleMons, gBattleMons, SIZE_G_BATTLE_MONS);
return savedBattleMons;
}

void FreeRestoreBattleMons(struct BattlePokemon *savedBattleMons)
{
memcpy(gBattleMons, savedBattleMons, SIZE_G_BATTLE_MONS);
Free(savedBattleMons);
}

// party logic
s32 AI_CalcPartyMonDamage(u16 move, u8 battlerAtk, u8 battlerDef, struct Pokemon *mon)
s32 AI_CalcPartyMonBestMoveDamage(u32 battlerAtk, u32 battlerDef, struct Pokemon *attackerMon, struct Pokemon *targetMon)
{
s32 dmg;
u32 i;
s32 i, move, bestDmg, dmg;
u8 effectiveness;
struct BattlePokemon *battleMons = Alloc(sizeof(struct BattlePokemon) * MAX_BATTLERS_COUNT);

for (i = 0; i < MAX_BATTLERS_COUNT; i++)
battleMons[i] = gBattleMons[i];
struct BattlePokemon *savedBattleMons = AllocSaveBattleMons();

PokemonToBattleMon(mon, &gBattleMons[battlerAtk]);
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE);
if (attackerMon != NULL)
PokemonToBattleMon(attackerMon, &gBattleMons[battlerAtk]);
if (targetMon != NULL)
PokemonToBattleMon(targetMon, &gBattleMons[battlerDef]);

for (i = 0; i < MAX_BATTLERS_COUNT; i++)
gBattleMons[i] = battleMons[i];
for (bestDmg = 0, i = 0; i < MAX_MON_MOVES; i++)
{
if (BattlerHasAi(battlerAtk))
move = GetMonData(attackerMon, MON_DATA_MOVE1 + i);
else
move = AI_PARTY->mons[GET_BATTLER_SIDE2(battlerAtk)][gBattlerPartyIndexes[battlerAtk]].moves[i];

Free(battleMons);
if (move != MOVE_NONE && gBattleMoves[move].power != 0)
{
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE);
if (dmg > bestDmg)
bestDmg = dmg;
}
}

FreeRestoreBattleMons(savedBattleMons);
return dmg;
}

Expand Down
4 changes: 1 addition & 3 deletions src/battle_controller_opponent.c
Original file line number Diff line number Diff line change
Expand Up @@ -1688,7 +1688,6 @@ static void OpponentHandleChoosePokemon(void)
else if (*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) == PARTY_SIZE)
{
chosenMonId = GetMostSuitableMonToSwitchInto();

if (chosenMonId == PARTY_SIZE)
{
s32 battler1, battler2, firstId, lastId;
Expand All @@ -1702,14 +1701,13 @@ static void OpponentHandleChoosePokemon(void)
battler1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
battler2 = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
pokemonInBattle = 2;

}

GetAIPartyIndexes(gActiveBattler, &firstId, &lastId);

for (chosenMonId = (lastId-1); chosenMonId >= firstId; chosenMonId--)
{
if (GetMonData(&gEnemyParty[chosenMonId], MON_DATA_HP) != 0
if (IsValidForBattle(&gEnemyParty[chosenMonId])
&& chosenMonId != gBattlerPartyIndexes[battler1]
&& chosenMonId != gBattlerPartyIndexes[battler2]
&& (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON)
Expand Down

0 comments on commit a0dd77d

Please sign in to comment.