Truant not dumb

This commit is contained in:
DizzyEggg 2019-09-01 14:23:11 +02:00
parent a41ecf7101
commit cfb49cdd0b
3 changed files with 174 additions and 111 deletions

View File

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

View File

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

View File

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