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); bool32 ShouldUseWishAromatherapy(u8 battlerAtk, u8 battlerDef, u16 move);
// party logic // 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); s32 CountUsablePartyMons(u8 battlerId);
bool32 IsPartyFullyHealedExceptBattler(u8 battler); bool32 IsPartyFullyHealedExceptBattler(u8 battler);
bool32 PartyHasMoveSplit(u8 battlerId, u8 split); bool32 PartyHasMoveSplit(u8 battlerId, u8 split);

View File

@ -28,6 +28,7 @@ static bool8 ShouldUseItem(void);
static bool32 AiExpectsToFaintPlayer(void); static bool32 AiExpectsToFaintPlayer(void);
static bool32 AI_ShouldHeal(u32 healAmount); static bool32 AI_ShouldHeal(u32 healAmount);
static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount); static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount);
static bool32 IsAiPartyMonOHKOBy(u32 battlerAtk, struct Pokemon *aiMon);
static bool32 IsAceMon(u32 battlerId, u32 monPartyId) 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. // Find a Pokemon in the party that has a super effective move.
for (i = firstId; i < lastId; i++) for (i = firstId; i < lastId; i++)
{ {
if (GetMonData(&party[i], MON_DATA_HP) == 0) if (!IsValidForBattle(&party[i]))
continue;
if (GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE)
continue;
if (GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG)
continue; continue;
if (i == gBattlerPartyIndexes[gActiveBattler]) if (i == gBattlerPartyIndexes[gActiveBattler])
continue; continue;
@ -195,13 +192,9 @@ static bool8 FindMonThatAbsorbsOpponentsMove(void)
for (i = firstId; i < lastId; i++) for (i = firstId; i < lastId; i++)
{ {
u16 species;
u16 monAbility; u16 monAbility;
if (GetMonData(&party[i], MON_DATA_HP) == 0) if (!IsValidForBattle(&party[i]))
continue;
species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
if (species == SPECIES_NONE || species == SPECIES_EGG)
continue; continue;
if (i == gBattlerPartyIndexes[battlerIn1]) if (i == gBattlerPartyIndexes[battlerIn1])
continue; continue;
@ -215,7 +208,6 @@ static bool8 FindMonThatAbsorbsOpponentsMove(void)
continue; continue;
monAbility = GetMonAbility(&party[i]); monAbility = GetMonAbility(&party[i]);
if (absorbingTypeAbility == monAbility && Random() & 1) if (absorbingTypeAbility == monAbility && Random() & 1)
{ {
// we found a mon. // we found a mon.
@ -290,9 +282,7 @@ static bool8 ShouldSwitchIfGameStatePrompt(void)
continue; continue;
//Look for mon in party that is able to be switched into and has ability that sets terrain //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 if (IsValidForBattle(&party[i])
&& GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE
&& GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG
&& i != gBattlerPartyIndexes[gActiveBattler] && i != gBattlerPartyIndexes[gActiveBattler]
&& i != gBattlerPartyIndexes[BATTLE_PARTNER(gActiveBattler)] && i != gBattlerPartyIndexes[BATTLE_PARTNER(gActiveBattler)]
&& IsBattlerGrounded(gActiveBattler) && IsBattlerGrounded(gActiveBattler)
@ -561,13 +551,9 @@ static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent)
for (i = firstId; i < lastId; i++) for (i = firstId; i < lastId; i++)
{ {
u16 species; u16 species, monAbility;
u16 monAbility;
if (GetMonData(&party[i], MON_DATA_HP) == 0) if (!IsValidForBattle(&party[i]))
continue;
species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
if (species == SPECIES_NONE || species == SPECIES_EGG)
continue; continue;
if (i == gBattlerPartyIndexes[battlerIn1]) if (i == gBattlerPartyIndexes[battlerIn1])
continue; continue;
@ -580,8 +566,8 @@ static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent)
if (IsAceMon(gActiveBattler, i)) if (IsAceMon(gActiveBattler, i))
continue; continue;
species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
monAbility = GetMonAbility(&party[i]); monAbility = GetMonAbility(&party[i]);
CalcPartyMonTypeEffectivenessMultiplier(gLastLandedMoves[gActiveBattler], species, monAbility); CalcPartyMonTypeEffectivenessMultiplier(gLastLandedMoves[gActiveBattler], species, monAbility);
if (gMoveResultFlags & flags) if (gMoveResultFlags & flags)
{ {
@ -650,11 +636,7 @@ bool32 ShouldSwitch(void)
for (i = firstId; i < lastId; i++) for (i = firstId; i < lastId; i++)
{ {
if (GetMonData(&party[i], MON_DATA_HP) == 0) if (!IsValidForBattle(&party[i]))
continue;
if (GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE)
continue;
if (GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG)
continue; continue;
if (i == gBattlerPartyIndexes[battlerIn1]) if (i == gBattlerPartyIndexes[battlerIn1])
continue; continue;
@ -751,7 +733,7 @@ void AI_TrySwitchOrUseItem(void)
for (monToSwitchId = (lastId-1); monToSwitchId >= firstId; monToSwitchId--) for (monToSwitchId = (lastId-1); monToSwitchId >= firstId; monToSwitchId--)
{ {
if (GetMonData(&party[monToSwitchId], MON_DATA_HP) == 0) if (!IsValidForBattle(&party[monToSwitchId]))
continue; continue;
if (monToSwitchId == gBattlerPartyIndexes[battlerIn1]) if (monToSwitchId == gBattlerPartyIndexes[battlerIn1])
continue; 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 // 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. // 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; int i, j, bits = 0;
@ -793,6 +775,8 @@ static u32 GetBestMonBatonPass(struct Pokemon *party, int firstId, int lastId, u
{ {
if (invalidMons & gBitTable[i]) if (invalidMons & gBitTable[i])
continue; continue;
if (IsAiPartyMonOHKOBy(opposingBattler, &party[i]))
continue;
for (j = 0; j < MAX_MON_MOVES; j++) 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 defType1 = gSpeciesInfo[species].types[0];
u8 defType2 = gSpeciesInfo[species].types[1]; u8 defType2 = gSpeciesInfo[species].types[1];
if (IsAiPartyMonOHKOBy(opposingBattler, &party[i]))
continue;
typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType1))); typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType1)));
if (atkType2 != atkType1) if (atkType2 != atkType1)
typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType1))); 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) static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, u32 opposingBattler)
{ {
int i, j; int i, j;
int bestDmg = 0; int dmg, bestDmg = 0;
int bestMonId = PARTY_SIZE; int bestMonId = PARTY_SIZE;
gMoveResultFlags = 0; gMoveResultFlags = 0;
@ -890,19 +877,14 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva
{ {
if (gBitTable[i] & invalidMons) if (gBitTable[i] & invalidMons)
continue; 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); bestDmg = dmg;
if (move != MOVE_NONE && gBattleMoves[move].power != 0) bestMonId = i;
{
s32 dmg = AI_CalcPartyMonDamage(move, gActiveBattler, opposingBattler, &party[i]);
if (bestDmg < dmg)
{
bestDmg = dmg;
bestMonId = i;
}
}
} }
} }
@ -912,7 +894,7 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva
u8 GetMostSuitableMonToSwitchInto(void) u8 GetMostSuitableMonToSwitchInto(void)
{ {
u32 opposingBattler = 0; u32 opposingBattler = 0;
u32 bestMonId = 0; u32 bestMonId = PARTY_SIZE;
u8 battlerIn1 = 0, battlerIn2 = 0; u8 battlerIn1 = 0, battlerIn2 = 0;
s32 firstId = 0; s32 firstId = 0;
s32 lastId = 0; // + 1 s32 lastId = 0; // + 1
@ -954,10 +936,7 @@ u8 GetMostSuitableMonToSwitchInto(void)
// Get invalid slots ids. // Get invalid slots ids.
for (i = firstId; i < lastId; i++) for (i = firstId; i < lastId; i++)
{ {
u16 species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG); if (!IsValidForBattle(&party[i])
if (species == SPECIES_NONE
|| species == SPECIES_EGG
|| GetMonData(&party[i], MON_DATA_HP) == 0
|| gBattlerPartyIndexes[battlerIn1] == i || gBattlerPartyIndexes[battlerIn1] == i
|| gBattlerPartyIndexes[battlerIn2] == i || gBattlerPartyIndexes[battlerIn2] == i
|| i == *(gBattleStruct->monToSwitchIntoId + battlerIn1) || 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) if (bestMonId != PARTY_SIZE)
return bestMonId; return bestMonId;
@ -1040,9 +1019,7 @@ static bool8 ShouldUseItem(void)
for (i = 0; i < PARTY_SIZE; i++) for (i = 0; i < PARTY_SIZE; i++)
{ {
if (GetMonData(&party[i], MON_DATA_HP) != 0 if (IsValidForBattle(&party[i]))
&& GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE
&& GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG)
{ {
validMons++; validMons++;
} }
@ -1155,3 +1132,29 @@ static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount)
} }
return FALSE; 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; return FALSE;
} }
// party logic #define SIZE_G_BATTLE_MONS (sizeof(struct BattlePokemon) * MAX_BATTLERS_COUNT)
s32 AI_CalcPartyMonDamage(u16 move, u8 battlerAtk, u8 battlerDef, struct Pokemon *mon)
struct BattlePokemon *AllocSaveBattleMons(void)
{ {
s32 dmg; struct BattlePokemon *savedBattleMons = Alloc(SIZE_G_BATTLE_MONS);
u32 i; 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; u8 effectiveness;
struct BattlePokemon *battleMons = Alloc(sizeof(struct BattlePokemon) * MAX_BATTLERS_COUNT); struct BattlePokemon *savedBattleMons = AllocSaveBattleMons();
for (i = 0; i < MAX_BATTLERS_COUNT; i++) if (attackerMon != NULL)
battleMons[i] = gBattleMons[i]; PokemonToBattleMon(attackerMon, &gBattleMons[battlerAtk]);
if (targetMon != NULL)
PokemonToBattleMon(targetMon, &gBattleMons[battlerDef]);
PokemonToBattleMon(mon, &gBattleMons[battlerAtk]); for (bestDmg = 0, i = 0; i < MAX_MON_MOVES; i++)
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE); {
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++) if (move != MOVE_NONE && gBattleMoves[move].power != 0)
gBattleMons[i] = battleMons[i]; {
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE);
Free(battleMons); if (dmg > bestDmg)
bestDmg = dmg;
}
}
FreeRestoreBattleMons(savedBattleMons);
return dmg; return dmg;
} }

View File

@ -1688,7 +1688,6 @@ static void OpponentHandleChoosePokemon(void)
else if (*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) == PARTY_SIZE) else if (*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) == PARTY_SIZE)
{ {
chosenMonId = GetMostSuitableMonToSwitchInto(); chosenMonId = GetMostSuitableMonToSwitchInto();
if (chosenMonId == PARTY_SIZE) if (chosenMonId == PARTY_SIZE)
{ {
s32 battler1, battler2, firstId, lastId; s32 battler1, battler2, firstId, lastId;
@ -1702,14 +1701,13 @@ static void OpponentHandleChoosePokemon(void)
battler1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); battler1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
battler2 = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); battler2 = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
pokemonInBattle = 2; pokemonInBattle = 2;
} }
GetAIPartyIndexes(gActiveBattler, &firstId, &lastId); GetAIPartyIndexes(gActiveBattler, &firstId, &lastId);
for (chosenMonId = (lastId-1); chosenMonId >= firstId; chosenMonId--) for (chosenMonId = (lastId-1); chosenMonId >= firstId; chosenMonId--)
{ {
if (GetMonData(&gEnemyParty[chosenMonId], MON_DATA_HP) != 0 if (IsValidForBattle(&gEnemyParty[chosenMonId])
&& chosenMonId != gBattlerPartyIndexes[battler1] && chosenMonId != gBattlerPartyIndexes[battler1]
&& chosenMonId != gBattlerPartyIndexes[battler2] && chosenMonId != gBattlerPartyIndexes[battler2]
&& (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON) && (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON)