Improve AI switching, so it doesn't get killed on switch-in (#3146)

This commit is contained in:
ghoulslash 2023-08-04 07:34:58 -04:00 committed by GitHub
commit a0dd77dd41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 93 additions and 66 deletions

View File

@ -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);

View File

@ -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)
{
@ -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;
@ -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;
@ -215,7 +208,6 @@ static bool8 FindMonThatAbsorbsOpponentsMove(void)
continue;
monAbility = GetMonAbility(&party[i]);
if (absorbingTypeAbility == monAbility && Random() & 1)
{
// we found a mon.
@ -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)
@ -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;
@ -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)
{
@ -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;
@ -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;
@ -785,7 +767,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, u32 opposingBattler)
{
int i, j, bits = 0;
@ -793,6 +775,8 @@ static u32 GetBestMonBatonPass(struct Pokemon *party, int firstId, int lastId, u
{
if (invalidMons & gBitTable[i])
continue;
if (IsAiPartyMonOHKOBy(opposingBattler, &party[i]))
continue;
for (j = 0; j < MAX_MON_MOVES; j++)
{
@ -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)));
@ -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;
@ -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;
}
}
@ -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
@ -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)
@ -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;
@ -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++;
}
@ -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;
}

View File

@ -3369,25 +3369,49 @@ bool32 ShouldUseWishAromatherapy(u8 battlerAtk, u8 battlerDef, u16 move)
return FALSE;
}
// party logic
s32 AI_CalcPartyMonDamage(u16 move, u8 battlerAtk, u8 battlerDef, struct Pokemon *mon)
#define SIZE_G_BATTLE_MONS (sizeof(struct BattlePokemon) * MAX_BATTLERS_COUNT)
struct BattlePokemon *AllocSaveBattleMons(void)
{
s32 dmg;
u32 i;
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_CalcPartyMonBestMoveDamage(u32 battlerAtk, u32 battlerDef, struct Pokemon *attackerMon, struct Pokemon *targetMon)
{
s32 i, move, bestDmg, dmg;
u8 effectiveness;
struct BattlePokemon *battleMons = Alloc(sizeof(struct BattlePokemon) * MAX_BATTLERS_COUNT);
struct BattlePokemon *savedBattleMons = AllocSaveBattleMons();
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
battleMons[i] = gBattleMons[i];
if (attackerMon != NULL)
PokemonToBattleMon(attackerMon, &gBattleMons[battlerAtk]);
if (targetMon != NULL)
PokemonToBattleMon(targetMon, &gBattleMons[battlerDef]);
PokemonToBattleMon(mon, &gBattleMons[battlerAtk]);
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE);
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];
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
gBattleMons[i] = battleMons[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;
}

View File

@ -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;
@ -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)