merge ai move and item decisions. AI will prefer to defeat player over healing

This commit is contained in:
ghoulslash 2021-09-27 21:03:27 -04:00
parent b4e456aeca
commit e75a8fb67c
10 changed files with 350 additions and 288 deletions

View File

@ -3,9 +3,10 @@
// return values for BattleAI_ChooseMoveOrAction // return values for BattleAI_ChooseMoveOrAction
// 0 - 3 are move idx // 0 - 3 are move idx
#define AI_CHOICE_FLEE 4 #define AI_CHOICE_FLEE 4
#define AI_CHOICE_WATCH 5 #define AI_CHOICE_WATCH 5
#define AI_CHOICE_SWITCH 7 #define AI_CHOICE_SWITCH 7
#define AI_CHOICE_USE_ITEM 8
#define RETURN_SCORE_PLUS(val) \ #define RETURN_SCORE_PLUS(val) \
{ \ { \

View File

@ -32,7 +32,7 @@ enum {
}; };
void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId); void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId);
void AI_TrySwitchOrUseItem(void); u8 AI_TrySwitchOrUseItem(u8 currAction);
u8 GetMostSuitableMonToSwitchInto(void); u8 GetMostSuitableMonToSwitchInto(void);
bool32 ShouldSwitch(void); bool32 ShouldSwitch(void);

View File

@ -1,9 +1,9 @@
#ifndef GUARD_BATTLE_AI_UTIL_H #ifndef GUARD_BATTLE_AI_UTIL_H
#define GUARD_BATTLE_AI_UTIL_H #define GUARD_BATTLE_AI_UTIL_H
// for IsAiFaster // for AI_WhoStrikesFirst
#define AI_CHECK_FASTER 0 // if_user_faster #define AI_IS_FASTER 0
#define AI_CHECK_SLOWER 1 // if_target_faster #define AI_IS_SLOWER 1
#define FOE(battler) ((battler ^ BIT_SIDE) & BIT_SIDE) #define FOE(battler) ((battler ^ BIT_SIDE) & BIT_SIDE)
@ -21,12 +21,13 @@ void SaveBattlerData(u8 battlerId);
void SetBattlerData(u8 battlerId); void SetBattlerData(u8 battlerId);
void RestoreBattlerData(u8 battlerId); void RestoreBattlerData(u8 battlerId);
bool32 WillAIStrikeFirst(void);
u32 GetTotalBaseStat(u32 species); u32 GetTotalBaseStat(u32 species);
bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler); bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler);
bool32 AtMaxHp(u8 battler); bool32 AtMaxHp(u8 battler);
u32 GetHealthPercentage(u8 battler); u32 GetHealthPercentage(u8 battler);
bool32 IsBattlerTrapped(u8 battler, bool8 switching); bool32 IsBattlerTrapped(u8 battler, bool8 switching);
bool32 IsAiFaster(u8 battler); u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2);
bool32 CanTargetFaintAi(u8 battlerDef, u8 battlerAtk); bool32 CanTargetFaintAi(u8 battlerDef, u8 battlerAtk);
bool32 CanMoveFaintBattler(u16 move, u8 battlerDef, u8 battlerAtk, u8 nHits); bool32 CanMoveFaintBattler(u16 move, u8 battlerDef, u8 battlerAtk, u8 nHits);
bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgMod); bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgMod);

View File

@ -21,6 +21,8 @@ struct UnknownPokemonStruct4
/*0x1D*/ u8 language; /*0x1D*/ u8 language;
}; };
struct ChooseMoveStruct;
#define TYPE_NAME_LENGTH 6 #define TYPE_NAME_LENGTH 6
#define ABILITY_NAME_LENGTH 12 #define ABILITY_NAME_LENGTH 12
@ -70,6 +72,7 @@ void RunBattleScriptCommands(void);
bool8 TryRunFromBattle(u8 battlerId); bool8 TryRunFromBattle(u8 battlerId);
void SpecialStatusesClear(void); void SpecialStatusesClear(void);
void SetTypeBeforeUsingMove(u16 move, u8 battlerAtk); void SetTypeBeforeUsingMove(u16 move, u8 battlerAtk);
void FillChooseMoveStruct(struct ChooseMoveStruct *moveInfo);
extern struct UnknownPokemonStruct4 gMultiPartnerParty[MULTI_PARTY_SIZE]; extern struct UnknownPokemonStruct4 gMultiPartnerParty[MULTI_PARTY_SIZE];

View File

@ -210,6 +210,9 @@ u8 BattleAI_ChooseMoveOrAction(void)
else else
ret = ChooseMoveOrAction_Doubles(); ret = ChooseMoveOrAction_Doubles();
AI_THINKING_STRUCT->movesetIndex = ret;
AI_THINKING_STRUCT->moveConsidered = gBattleMons[sBattler_AI].moves[AI_THINKING_STRUCT->movesetIndex];
ret = AI_TrySwitchOrUseItem(ret);
gCurrentMove = savedCurrentMove; gCurrentMove = savedCurrentMove;
return ret; return ret;
} }
@ -272,47 +275,6 @@ static u8 ChooseMoveOrAction_Singles(void)
return AI_CHOICE_WATCH; return AI_CHOICE_WATCH;
gActiveBattler = sBattler_AI; gActiveBattler = sBattler_AI;
// If can switch.
if (CountUsablePartyMons(sBattler_AI) > 0
&& !IsAbilityPreventingEscape(sBattler_AI)
&& !(gBattleMons[gActiveBattler].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION))
&& !(gStatuses3[gActiveBattler] & STATUS3_ROOTED)
&& !(gBattleTypeFlags & (BATTLE_TYPE_ARENA | BATTLE_TYPE_PALACE))
&& AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS))
{
// Consider switching if all moves are worthless to use.
if (GetTotalBaseStat(gBattleMons[sBattler_AI].species) >= 310 // Mon is not weak.
&& gBattleMons[sBattler_AI].hp >= gBattleMons[sBattler_AI].maxHP / 2)
{
s32 cap = AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (AI_THINKING_STRUCT->score[i] > cap)
break;
}
if (i == MAX_MON_MOVES && GetMostSuitableMonToSwitchInto() != PARTY_SIZE)
{
AI_THINKING_STRUCT->switchMon = TRUE;
return AI_CHOICE_SWITCH;
}
}
// 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
&& gBattleMons[sBattler_AI].hp >= gBattleMons[sBattler_AI].maxHP / 2)
{
if (GetMostSuitableMonToSwitchInto() != PARTY_SIZE)
{
AI_THINKING_STRUCT->switchMon = TRUE;
return AI_CHOICE_SWITCH;
}
}
}
numOfBestMoves = 1; numOfBestMoves = 1;
currentMoveArray[0] = AI_THINKING_STRUCT->score[0]; currentMoveArray[0] = AI_THINKING_STRUCT->score[0];
consideredMoveArray[0] = 0; consideredMoveArray[0] = 0;
@ -546,7 +508,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
} }
// check off screen // check off screen
if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) != 1) if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER)
RETURN_SCORE_MINUS(20); // if target off screen and we go first, don't use move RETURN_SCORE_MINUS(20); // if target off screen and we go first, don't use move
// check if negates type // check if negates type
@ -1207,7 +1169,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
&& (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_MENTAL_HERB) && (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_MENTAL_HERB)
&& !PartnerHasSameMoveEffectWithoutTarget(AI_DATA->battlerAtkPartner, move, AI_DATA->partnerMove)) && !PartnerHasSameMoveEffectWithoutTarget(AI_DATA->battlerAtkPartner, move, AI_DATA->partnerMove))
{ {
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // attacker should go first if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first
{ {
if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF) if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF)
score -= 10; // no anticipated move to disable score -= 10; // no anticipated move to disable
@ -1227,7 +1189,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
&& (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_MENTAL_HERB) && (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_MENTAL_HERB)
&& !DoesPartnerHaveSameMoveEffect(AI_DATA->battlerAtkPartner, battlerDef, move, AI_DATA->partnerMove)) && !DoesPartnerHaveSameMoveEffect(AI_DATA->battlerAtkPartner, battlerDef, move, AI_DATA->partnerMove))
{ {
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // attacker should go first if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first
{ {
if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF) if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF)
score -= 10; // no anticipated move to encore score -= 10; // no anticipated move to encore
@ -1668,7 +1630,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break; break;
case EFFECT_SPITE: case EFFECT_SPITE:
case EFFECT_MIMIC: case EFFECT_MIMIC:
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // attacker should go first if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first
{ {
if (gLastMoves[battlerDef] == MOVE_NONE if (gLastMoves[battlerDef] == MOVE_NONE
|| gLastMoves[battlerDef] == 0xFFFF) || gLastMoves[battlerDef] == 0xFFFF)
@ -1825,7 +1787,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (isDoubleBattle) if (isDoubleBattle)
{ {
if (IsHazardMoveEffect(gBattleMoves[AI_DATA->partnerMove].effect) // partner is going to set up hazards if (IsHazardMoveEffect(gBattleMoves[AI_DATA->partnerMove].effect) // partner is going to set up hazards
&& GetWhoStrikesFirst(AI_DATA->battlerAtkPartner, battlerAtk, FALSE)) // partner is going to set up before the potential Defog && AI_WhoStrikesFirst(AI_DATA->battlerAtkPartner, battlerAtk) == AI_IS_FASTER) // partner is going to set up before the potential Defog
{ {
score -= 10; score -= 10;
break; // Don't use Defog if partner is going to set up hazards break; // Don't use Defog if partner is going to set up hazards
@ -1863,7 +1825,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break; break;
case EFFECT_SEMI_INVULNERABLE: case EFFECT_SEMI_INVULNERABLE:
if (predictedMove != MOVE_NONE if (predictedMove != MOVE_NONE
&& GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1 && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER
&& gBattleMoves[predictedMove].effect == EFFECT_SEMI_INVULNERABLE) && gBattleMoves[predictedMove].effect == EFFECT_SEMI_INVULNERABLE)
score -= 10; // Don't Fly/dig/etc if opponent is going to fly/dig/etc after you score -= 10; // Don't Fly/dig/etc if opponent is going to fly/dig/etc after you
@ -2044,7 +2006,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_ME_FIRST: case EFFECT_ME_FIRST:
if (predictedMove != MOVE_NONE) if (predictedMove != MOVE_NONE)
{ {
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1) if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER)
score -= 10; // Target is predicted to go first, Me First will fail score -= 10; // Target is predicted to go first, Me First will fail
else else
return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score); return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score);
@ -2231,7 +2193,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
} }
break; break;
case EFFECT_ELECTRIFY: case EFFECT_ELECTRIFY:
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER
//|| GetMoveTypeSpecial(battlerDef, predictedMove) == TYPE_ELECTRIC // Move will already be electric type //|| GetMoveTypeSpecial(battlerDef, predictedMove) == TYPE_ELECTRIC // Move will already be electric type
|| PartnerMoveIsSameAsAttacker(AI_DATA->battlerAtkPartner, battlerDef, move, AI_DATA->partnerMove)) || PartnerMoveIsSameAsAttacker(AI_DATA->battlerAtkPartner, battlerDef, move, AI_DATA->partnerMove))
score -= 10; score -= 10;
@ -2260,7 +2222,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_INSTRUCT: case EFFECT_INSTRUCT:
{ {
u16 instructedMove; u16 instructedMove;
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1) if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER)
instructedMove = predictedMove; instructedMove = predictedMove;
else else
instructedMove = gLastMoves[battlerDef]; instructedMove = gLastMoves[battlerDef];
@ -2300,21 +2262,21 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break; break;
case EFFECT_QUASH: case EFFECT_QUASH:
if (!isDoubleBattle if (!isDoubleBattle
|| GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1 || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER
|| PartnerMoveIsSameAsAttacker(AI_DATA->battlerAtkPartner, battlerDef, move, AI_DATA->partnerMove)) || PartnerMoveIsSameAsAttacker(AI_DATA->battlerAtkPartner, battlerDef, move, AI_DATA->partnerMove))
score -= 10; score -= 10;
break; break;
case EFFECT_AFTER_YOU: case EFFECT_AFTER_YOU:
if (!IsTargetingPartner(battlerAtk, battlerDef) if (!IsTargetingPartner(battlerAtk, battlerDef)
|| !isDoubleBattle || !isDoubleBattle
|| GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1 || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER
|| PartnerMoveIsSameAsAttacker(AI_DATA->battlerAtkPartner, battlerDef, move, AI_DATA->partnerMove)) || PartnerMoveIsSameAsAttacker(AI_DATA->battlerAtkPartner, battlerDef, move, AI_DATA->partnerMove))
score -= 10; score -= 10;
break; break;
case EFFECT_SUCKER_PUNCH: case EFFECT_SUCKER_PUNCH:
if (predictedMove != MOVE_NONE) if (predictedMove != MOVE_NONE)
{ {
if (IS_MOVE_STATUS(predictedMove) || GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1) // opponent going first if (IS_MOVE_STATUS(predictedMove) || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent going first
score -= 10; score -= 10;
} }
break; break;
@ -2362,11 +2324,11 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score--; score--;
break; break;
case EFFECT_VITAL_THROW: case EFFECT_VITAL_THROW:
if (IsAiFaster(AI_CHECK_FASTER) && GetHealthPercentage(battlerAtk) < 40) if (WillAIStrikeFirst() && GetHealthPercentage(battlerAtk) < 40)
score--; // don't want to move last score--; // don't want to move last
break; break;
case EFFECT_FLAIL: case EFFECT_FLAIL:
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1 // opponent should go first if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER // Opponent should go first
|| GetHealthPercentage(battlerAtk) > 50) || GetHealthPercentage(battlerAtk) > 50)
score -= 4; score -= 4;
break; break;
@ -2417,7 +2379,7 @@ static s16 AI_TryToFaint(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0) && gBattleMoves[move].effect != EFFECT_EXPLOSION) if (CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0) && gBattleMoves[move].effect != EFFECT_EXPLOSION)
{ {
// this move can faint the target // this move can faint the target
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 || GetMovePriority(battlerAtk, move) > 0) if (!WillAIStrikeFirst() || GetMovePriority(battlerAtk, move) > 0)
score += 4; // we go first or we're using priority move score += 4; // we go first or we're using priority move
else else
score += 2; score += 2;
@ -2446,7 +2408,7 @@ static s16 AI_TryToFaint(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
} }
//AI_TryToFaint_CheckIfDanger //AI_TryToFaint_CheckIfDanger
if (!IsAiFaster(AI_CHECK_FASTER) && CanTargetFaintAi(battlerDef, battlerAtk)) if (!WillAIStrikeFirst() && CanTargetFaintAi(battlerDef, battlerAtk))
{ // AI_TryToFaint_Danger { // AI_TryToFaint_Danger
if (GetMoveDamageResult(move) != MOVE_POWER_BEST) if (GetMoveDamageResult(move) != MOVE_POWER_BEST)
score--; score--;
@ -2493,7 +2455,7 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
// Ally decided to use Frost Breath on us. we must have Anger Point as our ability // Ally decided to use Frost Breath on us. we must have Anger Point as our ability
if (AI_DATA->atkAbility == ABILITY_ANGER_POINT) if (AI_DATA->atkAbility == ABILITY_ANGER_POINT)
{ {
if (GetWhoStrikesFirst(battlerAtk, battlerAtkPartner, TRUE) == 1) // partner moving first if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner) == AI_IS_SLOWER) // Partner moving first
{ {
// discourage raising our attack since it's about to be maxed out // discourage raising our attack since it's about to be maxed out
if (IsAttackBoostMoveEffect(effect)) if (IsAttackBoostMoveEffect(effect))
@ -2760,22 +2722,22 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_INSTRUCT: case EFFECT_INSTRUCT:
{ {
u16 instructedMove; u16 instructedMove;
if (GetWhoStrikesFirst(battlerAtk, battlerAtkPartner, TRUE) == 0) if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner) == AI_IS_FASTER)
instructedMove = AI_DATA->partnerMove; instructedMove = AI_DATA->partnerMove;
else else
instructedMove = gLastMoves[battlerAtkPartner]; instructedMove = gLastMoves[battlerAtkPartner];
if (instructedMove != MOVE_NONE if (instructedMove != MOVE_NONE
&& !IS_MOVE_STATUS(instructedMove) && !IS_MOVE_STATUS(instructedMove)
&& gBattleMoves[instructedMove].target & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY)) //Use instruct on multi-target moves && gBattleMoves[instructedMove].target & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY)) // Use instruct on multi-target moves
{ {
RETURN_SCORE_PLUS(1); RETURN_SCORE_PLUS(1);
} }
} }
break; break;
case EFFECT_AFTER_YOU: case EFFECT_AFTER_YOU:
if (GetWhoStrikesFirst(battlerAtkPartner, FOE(battlerAtkPartner), TRUE) == 1 // opponent mon 1 goes before partner if (AI_WhoStrikesFirst(battlerAtkPartner, FOE(battlerAtkPartner) == AI_IS_SLOWER) // Opponent mon 1 goes before partner
|| GetWhoStrikesFirst(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)), TRUE) == 1) // opponent mon 2 goes before partner || AI_WhoStrikesFirst(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)) == AI_IS_SLOWER)) // Opponent mon 2 goes before partner
{ {
if (gBattleMoves[AI_DATA->partnerMove].effect == EFFECT_COUNTER || gBattleMoves[AI_DATA->partnerMove].effect == EFFECT_MIRROR_COAT) if (gBattleMoves[AI_DATA->partnerMove].effect == EFFECT_COUNTER || gBattleMoves[AI_DATA->partnerMove].effect == EFFECT_MIRROR_COAT)
break; // These moves need to go last break; // These moves need to go last
@ -2801,7 +2763,9 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_EARTHQUAKE: case EFFECT_EARTHQUAKE:
case EFFECT_MAGNITUDE: case EFFECT_MAGNITUDE:
if (!IsBattlerGrounded(battlerAtkPartner) if (!IsBattlerGrounded(battlerAtkPartner)
|| (GetWhoStrikesFirst(battlerAtk, battlerAtkPartner, TRUE) == 1 && IsUngroundingEffect(gBattleMoves[AI_DATA->partnerMove].effect))) || (IsBattlerGrounded(battlerAtkPartner)
&& AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner) == AI_IS_SLOWER
&& IsUngroundingEffect(gBattleMoves[AI_DATA->partnerMove].effect)))
score += 2; score += 2;
else if (IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_FIRE) else if (IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_FIRE)
|| IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_ELECTRIC) || IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_ELECTRIC)
@ -2852,9 +2816,9 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
// check already dead // check already dead
if (!IsBattlerIncapacitated(battlerDef, AI_DATA->defAbility) if (!IsBattlerIncapacitated(battlerDef, AI_DATA->defAbility)
&& CanTargetFaintAi(battlerAtk, battlerDef) && CanTargetFaintAi(battlerAtk, battlerDef)
&& GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1) // opponent should go first && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent should go first
{ {
if (atkPriority > 0) if (atkPriority > 0)
score++; score++;
else else
score--; score--;
@ -2896,7 +2860,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
{ {
case ABILITY_MOXIE: case ABILITY_MOXIE:
case ABILITY_BEAST_BOOST: case ABILITY_BEAST_BOOST:
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // attacker should go first if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first
{ {
if (CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
score += 8; // prioritize killing target for stat boost score += 8; // prioritize killing target for stat boost
@ -2984,7 +2948,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break; break;
case EFFECT_SPEED_UP: case EFFECT_SPEED_UP:
case EFFECT_SPEED_UP_2: case EFFECT_SPEED_UP_2:
if (IsAiFaster(AI_CHECK_SLOWER)) if (!WillAIStrikeFirst())
{ {
if (!AI_RandLessThan(70)) if (!AI_RandLessThan(70))
score += 3; score += 3;
@ -3082,7 +3046,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break; break;
case EFFECT_SPEED_DOWN: case EFFECT_SPEED_DOWN:
case EFFECT_SPEED_DOWN_2: case EFFECT_SPEED_DOWN_2:
if (IsAiFaster(AI_CHECK_FASTER)) if (WillAIStrikeFirst())
score -= 3; score -= 3;
else if (!AI_RandLessThan(70)) else if (!AI_RandLessThan(70))
score += 2; score += 2;
@ -3319,7 +3283,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score += 2; score += 2;
break; break;
case EFFECT_SPEED_DOWN_HIT: case EFFECT_SPEED_DOWN_HIT:
if (IsAiFaster(AI_CHECK_FASTER)) if (WillAIStrikeFirst())
score -= 2; score -= 2;
else if (!AI_RandLessThan(70)) else if (!AI_RandLessThan(70))
score++; score++;
@ -3351,7 +3315,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score++; score++;
break; break;
case EFFECT_MIMIC: case EFFECT_MIMIC:
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER)
{ {
if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF) if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF)
return AI_CheckViability(battlerAtk, battlerDef, gLastMoves[battlerDef], score); return AI_CheckViability(battlerAtk, battlerDef, gLastMoves[battlerDef], score);
@ -3410,7 +3374,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (gDisableStructs[battlerDef].disableTimer == 0 if (gDisableStructs[battlerDef].disableTimer == 0
&& (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_MENTAL_HERB)) // mental herb && (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_MENTAL_HERB)) // mental herb
{ {
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // AI goes first if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // AI goes first
{ {
if (gLastMoves[battlerDef] != MOVE_NONE if (gLastMoves[battlerDef] != MOVE_NONE
&& gLastMoves[battlerDef] != 0xFFFF) && gLastMoves[battlerDef] != 0xFFFF)
@ -3460,11 +3424,11 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score++; score++;
break; break;
case EFFECT_SPEED_UP_HIT: case EFFECT_SPEED_UP_HIT:
if (AI_DATA->atkAbility == ABILITY_SERENE_GRACE && AI_DATA->defAbility != ABILITY_CONTRARY && IsAiFaster(AI_CHECK_SLOWER)) if (AI_DATA->atkAbility == ABILITY_SERENE_GRACE && AI_DATA->defAbility != ABILITY_CONTRARY && !WillAIStrikeFirst())
score += 3; score += 3;
break; break;
case EFFECT_DESTINY_BOND: case EFFECT_DESTINY_BOND:
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 && CanTargetFaintAi(battlerDef, battlerAtk)) if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER && CanTargetFaintAi(battlerDef, battlerAtk))
score += 3; score += 3;
break; break;
case EFFECT_SPITE: case EFFECT_SPITE:
@ -3712,7 +3676,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
&& AI_DATA->atkAbility != ABILITY_CONTRARY && AI_DATA->atkAbility != ABILITY_CONTRARY
&& CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
{ {
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // Attacker goes first if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first
score += 9; score += 9;
else else
score += 3; score += 3;
@ -3757,7 +3721,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score++; score++;
if (predictedMove != MOVE_NONE && !isDoubleBattle) if (predictedMove != MOVE_NONE && !isDoubleBattle)
{ {
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // Attacker goes first if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first
{ {
if (gBattleMoves[predictedMove].effect == EFFECT_EXPLOSION if (gBattleMoves[predictedMove].effect == EFFECT_EXPLOSION
|| gBattleMoves[predictedMove].effect == EFFECT_PROTECT) || gBattleMoves[predictedMove].effect == EFFECT_PROTECT)
@ -3824,7 +3788,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break; break;
case EFFECT_ATTRACT: case EFFECT_ATTRACT:
if (!isDoubleBattle && BattlerWillFaintFromSecondaryDamage(battlerDef, AI_DATA->defAbility) if (!isDoubleBattle && BattlerWillFaintFromSecondaryDamage(battlerDef, AI_DATA->defAbility)
&& GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1) // Target goes first && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Target goes first
break; // Don't use if the attract won't have a change to activate break; // Don't use if the attract won't have a change to activate
if (gBattleMons[battlerDef].status1 & STATUS1_ANY if (gBattleMons[battlerDef].status1 & STATUS1_ANY
@ -3869,7 +3833,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (isDoubleBattle) if (isDoubleBattle)
{ {
if (IsHazardMoveEffect(gBattleMoves[AI_DATA->partnerMove].effect) // Partner is going to set up hazards if (IsHazardMoveEffect(gBattleMoves[AI_DATA->partnerMove].effect) // Partner is going to set up hazards
&& GetWhoStrikesFirst(battlerAtk, AI_DATA->battlerAtkPartner, TRUE) == 1) // Partner going first && AI_WhoStrikesFirst(battlerAtk, AI_DATA->battlerAtkPartner) == AI_IS_SLOWER) // Partner going first
break; // Don't use Defog if partner is going to set up hazards break; // Don't use Defog if partner is going to set up hazards
} }
@ -4346,13 +4310,13 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score++; score++;
break; break;
case EFFECT_THROAT_CHOP: case EFFECT_THROAT_CHOP:
if (predictedMove != MOVE_NONE && TestMoveFlags(predictedMove, FLAG_SOUND) && GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) if (predictedMove != MOVE_NONE && TestMoveFlags(predictedMove, FLAG_SOUND) && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER)
score += 3; // Ai goes first and predicts the target will use a sound move score += 3; // Ai goes first and predicts the target will use a sound move
else if (TestMoveFlagsInMoveset(battlerDef, FLAG_SOUND)) else if (TestMoveFlagsInMoveset(battlerDef, FLAG_SOUND))
score += 3; score += 3;
break; break;
case EFFECT_HEAL_BLOCK: case EFFECT_HEAL_BLOCK:
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 && predictedMove != MOVE_NONE && IsHealingMoveEffect(gBattleMoves[predictedMove].effect)) if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER && predictedMove != MOVE_NONE && IsHealingMoveEffect(gBattleMoves[predictedMove].effect))
score += 3; // Try to cancel healing move score += 3; // Try to cancel healing move
else if (HasHealingEffect(battlerDef) || AI_DATA->defHoldEffect == HOLD_EFFECT_LEFTOVERS else if (HasHealingEffect(battlerDef) || AI_DATA->defHoldEffect == HOLD_EFFECT_LEFTOVERS
|| (AI_DATA->defHoldEffect == HOLD_EFFECT_BLACK_SLUDGE && IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON))) || (AI_DATA->defHoldEffect == HOLD_EFFECT_BLACK_SLUDGE && IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON)))
@ -4388,7 +4352,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break; break;
case EFFECT_QUASH: case EFFECT_QUASH:
if (isDoubleBattle if (isDoubleBattle
&& GetWhoStrikesFirst(AI_DATA->battlerAtkPartner, battlerDef, TRUE) == 1) // Attacker partner wouldn't go before target && AI_WhoStrikesFirst(AI_DATA->battlerAtkPartner, battlerDef) == AI_IS_SLOWER) // Attacker partner wouldn't go before target
score++; score++;
break; break;
case EFFECT_TAILWIND: case EFFECT_TAILWIND:
@ -4410,8 +4374,8 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (IsBattlerGrounded(battlerAtk) && HasDamagingMoveOfType(battlerDef, TYPE_ELECTRIC) if (IsBattlerGrounded(battlerAtk) && HasDamagingMoveOfType(battlerDef, TYPE_ELECTRIC)
&& !(AI_GetTypeEffectiveness(MOVE_EARTHQUAKE, battlerDef, battlerAtk) == AI_EFFECTIVENESS_x0)) // Doesn't resist ground move && !(AI_GetTypeEffectiveness(MOVE_EARTHQUAKE, battlerDef, battlerAtk) == AI_EFFECTIVENESS_x0)) // Doesn't resist ground move
{ {
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // Attacker goes first if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first
{ {
if (gBattleMoves[predictedMove].type == TYPE_GROUND) if (gBattleMoves[predictedMove].type == TYPE_GROUND)
score += 3; // Cause the enemy's move to fail score += 3; // Cause the enemy's move to fail
break; break;
@ -4425,7 +4389,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
} }
break; break;
case EFFECT_CAMOUFLAGE: case EFFECT_CAMOUFLAGE:
if (predictedMove != MOVE_NONE && GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 // Attacker goes first if (predictedMove != MOVE_NONE && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER // Attacker goes first
&& !IS_MOVE_STATUS(move) && AI_GetTypeEffectiveness(predictedMove, battlerDef, battlerAtk) != AI_EFFECTIVENESS_x0) && !IS_MOVE_STATUS(move) && AI_GetTypeEffectiveness(predictedMove, battlerDef, battlerAtk) != AI_EFFECTIVENESS_x0)
score++; score++;
break; break;
@ -4468,7 +4432,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
} }
break; break;
case EFFECT_FLAIL: case EFFECT_FLAIL:
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // Ai goes first if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Ai goes first
{ {
if (GetHealthPercentage(battlerAtk) < 20) if (GetHealthPercentage(battlerAtk) < 20)
score++; score++;
@ -4510,7 +4474,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score += 2; score += 2;
break; break;
case EFFECT_ENDEAVOR: case EFFECT_ENDEAVOR:
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1) // Opponent faster if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent faster
{ {
if (GetHealthPercentage(battlerAtk) < 40) if (GetHealthPercentage(battlerAtk) < 40)
score++; score++;
@ -4541,7 +4505,7 @@ static s16 AI_SetupFirstTurn(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
return score; return score;
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING
&& GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1 && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER
&& CanTargetFaintAi(battlerDef, battlerAtk) && CanTargetFaintAi(battlerDef, battlerAtk)
&& GetMovePriority(battlerAtk, move) == 0) && GetMovePriority(battlerAtk, move) == 0)
{ {

View File

@ -12,11 +12,82 @@
#include "constants/item_effects.h" #include "constants/item_effects.h"
#include "constants/items.h" #include "constants/items.h"
#include "constants/moves.h" #include "constants/moves.h"
#include "constants/battle_ai.h"
// this file's functions // this file's functions
static bool8 HasSuperEffectiveMoveAgainstOpponents(bool8 noRng); static bool8 HasSuperEffectiveMoveAgainstOpponents(bool8 noRng);
static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent); static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent);
static bool8 ShouldUseItem(void); static bool8 ShouldUseItem(void);
static bool32 AI_ShouldHeal(u8 healAmount);
// Functions
u8 AI_TrySwitchOrUseItem(u8 currAction)
{
struct Pokemon *party;
u8 battlerIn1, battlerIn2;
s32 firstId;
s32 lastId; // + 1
u8 battlerIdentity = GetBattlerPosition(gActiveBattler);
if (!(gBattleTypeFlags & BATTLE_TYPE_TRAINER))
return currAction;
if (GetBattlerSide(gActiveBattler) == B_SIDE_PLAYER)
party = gPlayerParty; // Player's partner
else
party = gEnemyParty; // Enemy trainer
// Switching logic
if (ShouldSwitch())
{
if (*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) == PARTY_SIZE)
{
s32 monToSwitchId = GetMostSuitableMonToSwitchInto();
if (monToSwitchId == PARTY_SIZE)
{
if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE))
{
battlerIn1 = GetBattlerAtPosition(battlerIdentity);
battlerIn2 = battlerIn1;
}
else
{
battlerIn1 = GetBattlerAtPosition(battlerIdentity);
battlerIn2 = GetBattlerAtPosition(battlerIdentity ^ BIT_FLANK);
}
GetAIPartyIndexes(gActiveBattler, &firstId, &lastId);
for (monToSwitchId = firstId; monToSwitchId < lastId; monToSwitchId++)
{
if (GetMonData(&party[monToSwitchId], MON_DATA_HP) == 0)
continue;
if (monToSwitchId == gBattlerPartyIndexes[battlerIn1])
continue;
if (monToSwitchId == gBattlerPartyIndexes[battlerIn2])
continue;
if (monToSwitchId == *(gBattleStruct->monToSwitchIntoId + battlerIn1))
continue;
if (monToSwitchId == *(gBattleStruct->monToSwitchIntoId + battlerIn2))
continue;
break;
}
}
*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = monToSwitchId;
}
*(gBattleStruct->monToSwitchIntoId + gActiveBattler) = *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler);
return AI_CHOICE_SWITCH;
}
// Item Logic
if (ShouldUseItem())
return AI_CHOICE_USE_ITEM;
return currAction;
}
void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId) void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId)
{ {
@ -39,11 +110,52 @@ void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId)
static bool8 ShouldSwitchIfAllBadMoves(void) static bool8 ShouldSwitchIfAllBadMoves(void)
{ {
if (gBattleResources->ai->switchMon) u32 i;
if (AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS))
{ {
gBattleResources->ai->switchMon = 0; if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
{
// TODO double battle bad move switching logic
}
else
{
// Single battle. gActiveBattler is the enemy's battler id
if (GetTotalBaseStat(gBattleMons[gActiveBattler].species) >= 310 // Mon is not weak.
&& gBattleMons[gActiveBattler].hp >= gBattleMons[gActiveBattler].maxHP / 2)
{
s32 cap = AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (AI_THINKING_STRUCT->score[i] > cap)
break;
}
if (i == MAX_MON_MOVES && GetMostSuitableMonToSwitchInto() != PARTY_SIZE)
{
AI_THINKING_STRUCT->switchMon = TRUE;
}
}
// 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(gActiveBattler) == ABILITY_TRUANT
&& IsTruantMonVulnerable(gActiveBattler, gBattlerTarget)
&& gDisableStructs[gActiveBattler].truantCounter
&& gBattleMons[gActiveBattler].hp >= gBattleMons[gActiveBattler].maxHP / 2)
{
if (GetMostSuitableMonToSwitchInto() != PARTY_SIZE)
{
AI_THINKING_STRUCT->switchMon = TRUE;
}
}
}
}
if (AI_THINKING_STRUCT->switchMon)
{
AI_THINKING_STRUCT->switchMon = FALSE;
*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE; *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE;
BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0);
return TRUE; return TRUE;
} }
else else
@ -58,7 +170,6 @@ static bool8 ShouldSwitchIfPerishSong(void)
&& gDisableStructs[gActiveBattler].perishSongTimer == 0) && gDisableStructs[gActiveBattler].perishSongTimer == 0)
{ {
*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE; *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE;
BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0);
return TRUE; return TRUE;
} }
else else
@ -125,7 +236,6 @@ static bool8 ShouldSwitchIfWonderGuard(void)
{ {
// We found a mon. // We found a mon.
*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = i; *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = i;
BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0);
return TRUE; return TRUE;
} }
} }
@ -216,7 +326,6 @@ static bool8 FindMonThatAbsorbsOpponentsMove(void)
{ {
// we found a mon. // we found a mon.
*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = i; *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = i;
BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0);
return TRUE; return TRUE;
} }
} }
@ -236,13 +345,11 @@ static bool8 ShouldSwitchIfNaturalCure(void)
if ((gLastLandedMoves[gActiveBattler] == 0 || gLastLandedMoves[gActiveBattler] == 0xFFFF) && Random() & 1) if ((gLastLandedMoves[gActiveBattler] == 0 || gLastLandedMoves[gActiveBattler] == 0xFFFF) && Random() & 1)
{ {
*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE; *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE;
BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0);
return TRUE; return TRUE;
} }
else if (gBattleMoves[gLastLandedMoves[gActiveBattler]].power == 0 && Random() & 1) else if (gBattleMoves[gLastLandedMoves[gActiveBattler]].power == 0 && Random() & 1)
{ {
*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE; *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE;
BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0);
return TRUE; return TRUE;
} }
@ -254,7 +361,6 @@ static bool8 ShouldSwitchIfNaturalCure(void)
if (Random() & 1) if (Random() & 1)
{ {
*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE; *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE;
BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0);
return TRUE; return TRUE;
} }
@ -407,7 +513,6 @@ static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent)
if (AI_GetTypeEffectiveness(move, gActiveBattler, battlerIn1) >= UQ_4_12(2.0) && Random() % moduloPercent == 0) if (AI_GetTypeEffectiveness(move, gActiveBattler, battlerIn1) >= UQ_4_12(2.0) && Random() % moduloPercent == 0)
{ {
*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = i; *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = i;
BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0);
return TRUE; return TRUE;
} }
} }
@ -436,6 +541,11 @@ bool32 ShouldSwitch(void)
return FALSE; return FALSE;
availableToSwitch = 0; availableToSwitch = 0;
AI_THINKING_STRUCT->switchMon = FALSE;
if (CountUsablePartyMons(gActiveBattler) == 0) // No pokemon to switch to
return FALSE;
if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
{ {
battlerIn1 = gActiveBattler; battlerIn1 = gActiveBattler;
@ -500,73 +610,6 @@ bool32 ShouldSwitch(void)
return FALSE; return FALSE;
} }
void AI_TrySwitchOrUseItem(void)
{
struct Pokemon *party;
u8 battlerIn1, battlerIn2;
s32 firstId;
s32 lastId; // + 1
u8 battlerIdentity = GetBattlerPosition(gActiveBattler);
if (GetBattlerSide(gActiveBattler) == B_SIDE_PLAYER)
party = gPlayerParty;
else
party = gEnemyParty;
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER)
{
if (ShouldSwitch())
{
if (*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) == PARTY_SIZE)
{
s32 monToSwitchId = GetMostSuitableMonToSwitchInto();
if (monToSwitchId == PARTY_SIZE)
{
if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE))
{
battlerIn1 = GetBattlerAtPosition(battlerIdentity);
battlerIn2 = battlerIn1;
}
else
{
battlerIn1 = GetBattlerAtPosition(battlerIdentity);
battlerIn2 = GetBattlerAtPosition(battlerIdentity ^ BIT_FLANK);
}
GetAIPartyIndexes(gActiveBattler, &firstId, &lastId);
for (monToSwitchId = firstId; monToSwitchId < lastId; monToSwitchId++)
{
if (GetMonData(&party[monToSwitchId], MON_DATA_HP) == 0)
continue;
if (monToSwitchId == gBattlerPartyIndexes[battlerIn1])
continue;
if (monToSwitchId == gBattlerPartyIndexes[battlerIn2])
continue;
if (monToSwitchId == *(gBattleStruct->monToSwitchIntoId + battlerIn1))
continue;
if (monToSwitchId == *(gBattleStruct->monToSwitchIntoId + battlerIn2))
continue;
break;
}
}
*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = monToSwitchId;
}
*(gBattleStruct->monToSwitchIntoId + gActiveBattler) = *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler);
return;
}
else if (ShouldUseItem())
{
return;
}
}
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 // 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)
@ -839,20 +882,10 @@ static bool8 ShouldUseItem(void)
switch (*(gBattleStruct->AI_itemType + gActiveBattler / 2)) switch (*(gBattleStruct->AI_itemType + gActiveBattler / 2))
{ {
case AI_ITEM_FULL_RESTORE: case AI_ITEM_FULL_RESTORE:
if (gBattleMons[gActiveBattler].hp >= gBattleMons[gActiveBattler].maxHP / 4) shouldUse = AI_ShouldHeal(0);
break;
if (gBattleMons[gActiveBattler].hp == 0)
break;
shouldUse = TRUE;
break; break;
case AI_ITEM_HEAL_HP: case AI_ITEM_HEAL_HP:
paramOffset = GetItemEffectParamOffset(item, 4, 4); shouldUse = AI_ShouldHeal(itemEffects[GetItemEffectParamOffset(item, 4, 4)]);
if (paramOffset == 0)
break;
if (gBattleMons[gActiveBattler].hp == 0)
break;
if (gBattleMons[gActiveBattler].hp < gBattleMons[gActiveBattler].maxHP / 4 || gBattleMons[gActiveBattler].maxHP - gBattleMons[gActiveBattler].hp > itemEffects[paramOffset])
shouldUse = TRUE;
break; break;
case AI_ITEM_CURE_CONDITION: case AI_ITEM_CURE_CONDITION:
*(gBattleStruct->AI_itemFlags + gActiveBattler / 2) = 0; *(gBattleStruct->AI_itemFlags + gActiveBattler / 2) = 0;
@ -934,7 +967,6 @@ static bool8 ShouldUseItem(void)
if (shouldUse) if (shouldUse)
{ {
BtlController_EmitTwoReturnValues(1, B_ACTION_USE_ITEM, 0);
*(gBattleStruct->chosenItem + (gActiveBattler / 2) * 2) = item; *(gBattleStruct->chosenItem + (gActiveBattler / 2) * 2) = item;
gBattleResources->battleHistory->trainerItems[i] = 0; gBattleResources->battleHistory->trainerItems[i] = 0;
return shouldUse; return shouldUse;
@ -943,3 +975,41 @@ static bool8 ShouldUseItem(void)
return FALSE; return FALSE;
} }
static bool32 AI_ShouldHeal(u8 healAmount)
{
bool32 shouldHeal = FALSE;
u32 i;
if (gBattleMons[gActiveBattler].hp < gBattleMons[gActiveBattler].maxHP / 4
|| gBattleMons[gActiveBattler].hp == 0
|| (healAmount != 0 && gBattleMons[gActiveBattler].maxHP - gBattleMons[gActiveBattler].hp > healAmount))
{
// We have low enough HP to consider healing
shouldHeal = TRUE;
// Check special cases to NOT heal
for (i = 0; i < gBattlersCount; i++)
{
if (GetBattlerSide(i) == B_SIDE_PLAYER)
{
if (CanTargetFaintAiWithMod(i, gActiveBattler, healAmount, 0))
{
// Target is expected to faint us even after we heal. So why bother.
shouldHeal = FALSE;
break;
}
// AI_THINKING_STRUCT->movesetIndex is the array index of the AI's chosen move
if (CanAttackerFaintTarget(gActiveBattler, i, AI_THINKING_STRUCT->movesetIndex, 0) && AI_WhoStrikesFirst(gActiveBattler, i) == AI_IS_FASTER)
{
// We can faint the target and move first -> don't heal
shouldHeal = FALSE;
break;
}
}
}
}
return shouldHeal;
}

View File

@ -439,6 +439,11 @@ static const u16 sOtherMoveCallingMoves[] =
}; };
// Functions // Functions
bool32 WillAIStrikeFirst(void)
{
return (AI_WhoStrikesFirst(sBattler_AI, gBattlerTarget) == AI_IS_FASTER);
}
bool32 AI_RandLessThan(u8 val) bool32 AI_RandLessThan(u8 val)
{ {
if ((Random() % 0xFF) < val) if ((Random() % 0xFF) < val)
@ -632,7 +637,7 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler)
u32 move = gBattleResources->battleHistory->usedMoves[opposingBattler][i]; u32 move = gBattleResources->battleHistory->usedMoves[opposingBattler][i];
if (gBattleMoves[move].effect == EFFECT_PROTECT && move != MOVE_ENDURE) if (gBattleMoves[move].effect == EFFECT_PROTECT && move != MOVE_ENDURE)
return TRUE; return TRUE;
if (gBattleMoves[move].effect == EFFECT_SEMI_INVULNERABLE && GetWhoStrikesFirst(battlerAI, opposingBattler, TRUE) == 1) if (gBattleMoves[move].effect == EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAI, opposingBattler) == AI_IS_SLOWER)
return TRUE; return TRUE;
} }
return FALSE; return FALSE;
@ -964,48 +969,43 @@ u8 AI_GetMoveEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef)
return damageVar; return damageVar;
} }
// AI_CHECK_FASTER: is user(ai) faster /* Checks to see if AI will move ahead of another battler
// AI_CHECK_SLOWER: is target faster * Output:
bool32 IsAiFaster(u8 battler) * AI_IS_FASTER: is user(ai) faster
* AI_IS_SLOWER: is target faster
*/
u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2)
{ {
u32 fasterAI = 0, fasterPlayer = 0, i; u32 fasterAI = 0, fasterPlayer = 0, i;
s8 prioAI, prioPlayer; s8 prioAI = 0;
s8 prioPlayer = 0;
// Check move priorities first. // Check move priorities first.
prioAI = GetMovePriority(sBattler_AI, AI_THINKING_STRUCT->moveConsidered); prioAI = GetMovePriority(battlerAI, AI_THINKING_STRUCT->moveConsidered);
SaveBattlerData(gBattlerTarget);
SetBattlerData(gBattlerTarget);
for (i = 0; i < MAX_MON_MOVES; i++) for (i = 0; i < MAX_MON_MOVES; i++)
{ {
if (gBattleMons[gBattlerTarget].moves[i] == 0 || gBattleMons[gBattlerTarget].moves[i] == 0xFFFF) if (gBattleMons[battler2].moves[i] == 0 || gBattleMons[battler2].moves[i] == 0xFFFF)
continue; continue;
prioPlayer = GetMovePriority(gBattlerTarget, gBattleMons[gBattlerTarget].moves[i]); prioPlayer = GetMovePriority(battler2, gBattleMons[battler2].moves[i]);
if (prioAI > prioPlayer) if (prioAI > prioPlayer)
fasterAI++; fasterAI++;
else if (prioPlayer > prioAI) else if (prioPlayer > prioAI)
fasterPlayer++; fasterPlayer++;
} }
RestoreBattlerData(gBattlerTarget);
if (fasterAI > fasterPlayer) if (fasterAI > fasterPlayer)
{ {
if (battler == 0) // is user (ai) faster return AI_IS_FASTER;
return TRUE;
else
return FALSE;
} }
else if (fasterAI < fasterPlayer) else if (fasterAI < fasterPlayer)
{ {
if (battler == 1) // is target (player) faster return AI_IS_SLOWER;
return TRUE;
else
return FALSE;
} }
else else
{ {
// Priorities are the same(at least comparing to moves the AI is aware of), decide by speed. // Priorities are the same(at least comparing to moves the AI is aware of), decide by speed.
if (GetWhoStrikesFirst(sBattler_AI, gBattlerTarget, TRUE) == battler) if (GetWhoStrikesFirst(battlerAI, battler2, TRUE) == 0)
return TRUE; return TRUE;
else else
return FALSE; return FALSE;
@ -1045,14 +1045,17 @@ bool32 CanMoveFaintBattler(u16 move, u8 battlerDef, u8 battlerAtk, u8 nHits)
// Check if target has means to faint ai mon after modding hp/dmg // Check if target has means to faint ai mon after modding hp/dmg
bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgMod) bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgMod)
{ {
u32 i; u32 i, dmg;
u32 unusable = CheckMoveLimitations(battlerDef, 0, 0xFF & ~MOVE_LIMITATION_PP); u32 unusable = CheckMoveLimitations(battlerDef, 0, 0xFF & ~MOVE_LIMITATION_PP);
u16 *moves = gBattleResources->battleHistory->usedMoves[battlerDef]; u16 *moves = gBattleResources->battleHistory->usedMoves[battlerDef];
u32 hpCheck = gBattleMons[battlerAtk].hp + hpMod;
if (hpCheck > gBattleMons[battlerAtk].maxHP)
hpCheck = gBattleMons[battlerAtk].maxHP;
for (i = 0; i < MAX_MON_MOVES; i++) for (i = 0; i < MAX_MON_MOVES; i++)
{ {
u32 dmg = AI_CalcDamage(moves[i], battlerDef, battlerAtk); dmg = AI_THINKING_STRUCT->simulatedDmg[battlerAtk][battlerDef][i];
u32 hpCheck = gBattleMons[battlerAtk].hp + hpMod;
if (dmgMod) if (dmgMod)
dmg *= dmgMod; dmg *= dmgMod;
@ -1650,7 +1653,7 @@ u32 CountNegativeStatStages(u8 battlerId)
bool32 ShouldLowerAttack(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerAttack(u8 battlerAtk, u8 battlerDef, u16 defAbility)
{ {
if (IsAiFaster(AI_CHECK_FASTER) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (WillAIStrikeFirst() && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
return FALSE; // Don't bother lowering stats if can kill enemy. return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_ATK] > 4 if (gBattleMons[battlerDef].statStages[STAT_ATK] > 4
@ -1666,7 +1669,7 @@ bool32 ShouldLowerAttack(u8 battlerAtk, u8 battlerDef, u16 defAbility)
bool32 ShouldLowerDefense(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerDefense(u8 battlerAtk, u8 battlerDef, u16 defAbility)
{ {
if (IsAiFaster(AI_CHECK_FASTER) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (WillAIStrikeFirst() && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
return FALSE; // Don't bother lowering stats if can kill enemy. return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_DEF] > 4 if (gBattleMons[battlerDef].statStages[STAT_DEF] > 4
@ -1682,10 +1685,10 @@ bool32 ShouldLowerDefense(u8 battlerAtk, u8 battlerDef, u16 defAbility)
bool32 ShouldLowerSpeed(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerSpeed(u8 battlerAtk, u8 battlerDef, u16 defAbility)
{ {
if (IsAiFaster(AI_CHECK_FASTER) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (WillAIStrikeFirst() && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
return FALSE; // Don't bother lowering stats if can kill enemy. return FALSE; // Don't bother lowering stats if can kill enemy.
if (IsAiFaster(AI_CHECK_SLOWER) if (!WillAIStrikeFirst()
&& defAbility != ABILITY_CONTRARY && defAbility != ABILITY_CONTRARY
&& defAbility != ABILITY_CLEAR_BODY && defAbility != ABILITY_CLEAR_BODY
//&& defAbility != ABILITY_FULL_METAL_BODY //&& defAbility != ABILITY_FULL_METAL_BODY
@ -1696,7 +1699,7 @@ bool32 ShouldLowerSpeed(u8 battlerAtk, u8 battlerDef, u16 defAbility)
bool32 ShouldLowerSpAtk(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerSpAtk(u8 battlerAtk, u8 battlerDef, u16 defAbility)
{ {
if (IsAiFaster(AI_CHECK_FASTER) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (WillAIStrikeFirst() && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
return FALSE; // Don't bother lowering stats if can kill enemy. return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_SPATK] > 4 if (gBattleMons[battlerDef].statStages[STAT_SPATK] > 4
@ -1711,7 +1714,7 @@ bool32 ShouldLowerSpAtk(u8 battlerAtk, u8 battlerDef, u16 defAbility)
bool32 ShouldLowerSpDef(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerSpDef(u8 battlerAtk, u8 battlerDef, u16 defAbility)
{ {
if (IsAiFaster(AI_CHECK_FASTER) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (WillAIStrikeFirst() && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
return FALSE; // Don't bother lowering stats if can kill enemy. return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_SPDEF] > 4 if (gBattleMons[battlerDef].statStages[STAT_SPDEF] > 4
@ -1726,7 +1729,7 @@ bool32 ShouldLowerSpDef(u8 battlerAtk, u8 battlerDef, u16 defAbility)
bool32 ShouldLowerAccuracy(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerAccuracy(u8 battlerAtk, u8 battlerDef, u16 defAbility)
{ {
if (IsAiFaster(AI_CHECK_FASTER) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (WillAIStrikeFirst() && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
return FALSE; // Don't bother lowering stats if can kill enemy. return FALSE; // Don't bother lowering stats if can kill enemy.
if (defAbility != ABILITY_CONTRARY if (defAbility != ABILITY_CONTRARY
@ -1740,7 +1743,7 @@ bool32 ShouldLowerAccuracy(u8 battlerAtk, u8 battlerDef, u16 defAbility)
bool32 ShouldLowerEvasion(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerEvasion(u8 battlerAtk, u8 battlerDef, u16 defAbility)
{ {
if (IsAiFaster(AI_CHECK_FASTER) && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) if (WillAIStrikeFirst() && CanAttackerFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
return FALSE; // Don't bother lowering stats if can kill enemy. return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_EVASION] > DEFAULT_STAT_STAGE if (gBattleMons[battlerDef].statStages[STAT_EVASION] > DEFAULT_STAT_STAGE
@ -2404,7 +2407,7 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo
/*if (IsPredictedToSwitch(battlerDef, battlerAtk) && !hasStatBoost) /*if (IsPredictedToSwitch(battlerDef, battlerAtk) && !hasStatBoost)
return PIVOT; // Try pivoting so you can switch to a better matchup to counter your new opponent*/ return PIVOT; // Try pivoting so you can switch to a better matchup to counter your new opponent*/
if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // Attacker goes first if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first
{ {
if (!CanAttackerFaintTarget(battlerAtk, battlerDef, moveIndex, 0)) // Can't KO foe otherwise if (!CanAttackerFaintTarget(battlerAtk, battlerDef, moveIndex, 0)) // Can't KO foe otherwise
{ {
@ -2753,7 +2756,7 @@ u32 ShouldTryToFlinch(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbili
{ {
if (defAbility == ABILITY_INNER_FOCUS if (defAbility == ABILITY_INNER_FOCUS
|| DoesSubstituteBlockMove(battlerAtk, battlerDef, move) || DoesSubstituteBlockMove(battlerAtk, battlerDef, move)
|| GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1) // opponent goes first || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent goes first
{ {
return 0; // don't try to flinch return 0; // don't try to flinch
} }
@ -2877,7 +2880,7 @@ bool32 ShouldUseRecoilMove(u8 battlerAtk, u8 battlerDef, u32 recoilDmg, u8 moveI
bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage) bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage)
{ {
if (move == 0xFFFF || GetWhoStrikesFirst(battlerAtk, gBattlerTarget, TRUE) == 0) if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER)
{ {
// using item or user goes first // using item or user goes first
u8 healPercent = (gBattleMoves[move].argument == 0) ? 50 : gBattleMoves[move].argument; u8 healPercent = (gBattleMoves[move].argument == 0) ? 50 : gBattleMoves[move].argument;
@ -2904,7 +2907,7 @@ bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage)
bool32 ShouldRecover(u8 battlerAtk, u8 battlerDef, u16 move, u8 healPercent) bool32 ShouldRecover(u8 battlerAtk, u8 battlerDef, u16 move, u8 healPercent)
{ {
if (move == 0xFFFF || GetWhoStrikesFirst(battlerAtk, gBattlerTarget, TRUE) == 0) if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER)
{ {
// using item or user going first // using item or user going first
s32 damage = AI_THINKING_STRUCT->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]; s32 damage = AI_THINKING_STRUCT->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex];
@ -3350,7 +3353,7 @@ void IncreaseStatUpScore(u8 battlerAtk, u8 battlerDef, u8 statId, s16 *score)
} }
break; break;
case STAT_SPEED: case STAT_SPEED:
if (IsAiFaster(AI_CHECK_SLOWER)) if (!WillAIStrikeFirst())
{ {
if (gBattleMons[battlerAtk].statStages[STAT_SPEED] < STAT_UP_2_STAGE) if (gBattleMons[battlerAtk].statStages[STAT_SPEED] < STAT_UP_2_STAGE)
*score += 2; *score += 2;

View File

@ -1544,47 +1544,34 @@ static void OpponentHandlePrintSelectionString(void)
} }
static void OpponentHandleChooseAction(void) static void OpponentHandleChooseAction(void)
{
AI_TrySwitchOrUseItem();
OpponentBufferExecCompleted();
}
static void OpponentHandleYesNoBox(void)
{
OpponentBufferExecCompleted();
}
static void OpponentHandleChooseMove(void)
{ {
if (gBattleTypeFlags & BATTLE_TYPE_PALACE) if (gBattleTypeFlags & BATTLE_TYPE_PALACE)
{ {
BtlController_EmitTwoReturnValues(1, 10, ChooseMoveAndTargetInBattlePalace()); BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, ChooseMoveAndTargetInBattlePalace());
OpponentBufferExecCompleted();
} }
else else
{ {
u8 chosenMoveId; u8 chosenMoveId;
struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct*)(&gBattleResources->bufferA[gActiveBattler][4]); struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct*)(&gBattleResources->bufferA[gActiveBattler][4]);
if (gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER)) if (gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER))
{ {
BattleAI_SetupAIData(0xF); BattleAI_SetupAIData(0xF);
chosenMoveId = BattleAI_ChooseMoveOrAction(); chosenMoveId = BattleAI_ChooseMoveOrAction();
switch (chosenMoveId) switch (chosenMoveId)
{ {
case AI_CHOICE_USE_ITEM:
BtlController_EmitTwoReturnValues(1, B_ACTION_USE_ITEM, 0);
break;
case AI_CHOICE_SWITCH:
BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0);
break;
case AI_CHOICE_WATCH: case AI_CHOICE_WATCH:
BtlController_EmitTwoReturnValues(1, B_ACTION_SAFARI_WATCH_CAREFULLY, 0); BtlController_EmitTwoReturnValues(1, B_ACTION_SAFARI_WATCH_CAREFULLY, 0);
break; break;
case AI_CHOICE_FLEE: case AI_CHOICE_FLEE:
BtlController_EmitTwoReturnValues(1, B_ACTION_RUN, 0); BtlController_EmitTwoReturnValues(1, B_ACTION_RUN, 0);
break; break;
case AI_CHOICE_SWITCH:
BtlController_EmitTwoReturnValues(1, 10, 0xFFFF);
break;
case 6:
BtlController_EmitTwoReturnValues(1, 15, gBattlerTarget);
break;
default: default:
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER))
gBattlerTarget = gActiveBattler; gBattlerTarget = gActiveBattler;
@ -1594,15 +1581,16 @@ static void OpponentHandleChooseMove(void)
if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) if (gAbsentBattlerFlags & gBitTable[gBattlerTarget])
gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT); gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT);
} }
if (CanMegaEvolve(gActiveBattler)) // If opponent can mega evolve, do it.
BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8)); if (CanMegaEvolve(gActiveBattler)) { // If opponent can mega evolve, do it.
else BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8));
BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (gBattlerTarget << 8)); } else {
BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (chosenMoveId) | (gBattlerTarget << 8));
}
break; break;
} }
OpponentBufferExecCompleted();
} }
else else // Wild pokemon - use random move
{ {
u16 move; u16 move;
do do
@ -1612,15 +1600,27 @@ static void OpponentHandleChooseMove(void)
} while (move == MOVE_NONE); } while (move == MOVE_NONE);
if (gBattleMoves[move].target & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) if (gBattleMoves[move].target & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER))
BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (gActiveBattler << 8)); BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (chosenMoveId) | (gActiveBattler << 8));
else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (GetBattlerAtPosition(Random() & 2) << 8)); BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (chosenMoveId) | (GetBattlerAtPosition(Random() & 2) << 8));
else else
BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (GetBattlerAtPosition(B_POSITION_PLAYER_LEFT) << 8)); BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (chosenMoveId) | (GetBattlerAtPosition(B_POSITION_PLAYER_LEFT) << 8));
OpponentBufferExecCompleted();
} }
} }
OpponentBufferExecCompleted();
}
static void OpponentHandleYesNoBox(void)
{
OpponentBufferExecCompleted();
}
static void OpponentHandleChooseMove(void)
{
u8 *bufferB = gBattleResources->bufferB[gActiveBattler];
BtlController_EmitTwoReturnValues(1, 10, bufferB[2] | (bufferB[3] << 8));
OpponentBufferExecCompleted();
} }
static void OpponentHandleChooseItem(void) static void OpponentHandleChooseItem(void)

View File

@ -1509,7 +1509,37 @@ static void PlayerPartnerHandlePrintSelectionString(void)
static void PlayerPartnerHandleChooseAction(void) static void PlayerPartnerHandleChooseAction(void)
{ {
AI_TrySwitchOrUseItem(); u8 chosenMoveId;
struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct*)(&gBattleResources->bufferA[gActiveBattler][4]);
BattleAI_SetupAIData(0xF);
chosenMoveId = BattleAI_ChooseMoveOrAction();
switch (chosenMoveId)
{
case AI_CHOICE_USE_ITEM:
BtlController_EmitTwoReturnValues(1, B_ACTION_USE_ITEM, 0);
break;
case AI_CHOICE_SWITCH:
BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0);
break;
default:
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED))
gBattlerTarget = gActiveBattler;
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH)
{
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
if (gAbsentBattlerFlags & gBitTable[gBattlerTarget])
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
}
if (CanMegaEvolve(gActiveBattler)) // If partner can mega evolve, do it.
BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8));
else
BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (chosenMoveId) | (gBattlerTarget << 8));
break;
}
PlayerPartnerBufferExecCompleted(); PlayerPartnerBufferExecCompleted();
} }
@ -1520,25 +1550,6 @@ static void PlayerPartnerHandleYesNoBox(void)
static void PlayerPartnerHandleChooseMove(void) static void PlayerPartnerHandleChooseMove(void)
{ {
u8 chosenMoveId;
struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct*)(&gBattleResources->bufferA[gActiveBattler][4]);
BattleAI_SetupAIData(0xF);
chosenMoveId = BattleAI_ChooseMoveOrAction();
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED))
gBattlerTarget = gActiveBattler;
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH)
{
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
if (gAbsentBattlerFlags & gBitTable[gBattlerTarget])
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
}
if (CanMegaEvolve(gActiveBattler)) // If partner can mega evolve, do it.
BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8));
else
BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (gBattlerTarget << 8));
PlayerPartnerBufferExecCompleted(); PlayerPartnerBufferExecCompleted();
} }

View File

@ -3828,22 +3828,7 @@ static void HandleTurnActionSelectionState(void)
{ {
struct ChooseMoveStruct moveInfo; struct ChooseMoveStruct moveInfo;
moveInfo.mega = gBattleStruct->mega; FillChooseMoveStruct(&moveInfo);
moveInfo.species = gBattleMons[gActiveBattler].species;
moveInfo.monType1 = gBattleMons[gActiveBattler].type1;
moveInfo.monType2 = gBattleMons[gActiveBattler].type2;
moveInfo.monType3 = gBattleMons[gActiveBattler].type3;
for (i = 0; i < MAX_MON_MOVES; i++)
{
moveInfo.moves[i] = gBattleMons[gActiveBattler].moves[i];
moveInfo.currentPp[i] = gBattleMons[gActiveBattler].pp[i];
moveInfo.maxPp[i] = CalculatePPWithBonus(
gBattleMons[gActiveBattler].moves[i],
gBattleMons[gActiveBattler].ppBonuses,
i);
}
BtlController_EmitChooseMove(0, (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) != 0, FALSE, &moveInfo); BtlController_EmitChooseMove(0, (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) != 0, FALSE, &moveInfo);
MarkBattlerForControllerExec(gActiveBattler); MarkBattlerForControllerExec(gActiveBattler);
} }
@ -3945,6 +3930,9 @@ static void HandleTurnActionSelectionState(void)
return; return;
case B_ACTION_DEBUG: case B_ACTION_DEBUG:
BtlController_EmitDebugMenu(0); BtlController_EmitDebugMenu(0);
// fallthrough
case B_ACTION_SAFARI_WATCH_CAREFULLY:
case B_ACTION_RUN:
MarkBattlerForControllerExec(gActiveBattler); MarkBattlerForControllerExec(gActiveBattler);
break; break;
} }
@ -5158,3 +5146,24 @@ void SetTotemBoost(void)
} }
} }
} }
void FillChooseMoveStruct(struct ChooseMoveStruct * moveInfo)
{
int i;
moveInfo->mega = gBattleStruct->mega;
moveInfo->species = gBattleMons[gActiveBattler].species;
moveInfo->monType1 = gBattleMons[gActiveBattler].type1;
moveInfo->monType2 = gBattleMons[gActiveBattler].type2;
moveInfo->monType3 = gBattleMons[gActiveBattler].type3;
for (i = 0; i < MAX_MON_MOVES; i++)
{
moveInfo->moves[i] = gBattleMons[gActiveBattler].moves[i];
moveInfo->currentPp[i] = gBattleMons[gActiveBattler].pp[i];
moveInfo->maxPp[i] = CalculatePPWithBonus(
gBattleMons[gActiveBattler].moves[i],
gBattleMons[gActiveBattler].ppBonuses,
i);
}
}