move ai score calc to STATE_TURN_START_RECORD so AI can consider move selection for item use

This commit is contained in:
ghoulslash 2022-06-05 11:09:04 -04:00
parent c694e0cb90
commit b33a8b0cb4
10 changed files with 187 additions and 104 deletions

View File

@ -584,6 +584,8 @@ struct BattleStruct
bool8 spriteIgnore0Hp;
struct Illusion illusion[MAX_BATTLERS_COUNT];
s8 aiFinalScore[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // AI, target, moves to make debugging easier
u8 aiMoveOrAction[MAX_BATTLERS_COUNT];
u8 aiChosenTarget[MAX_BATTLERS_COUNT];
u8 soulheartBattlerId;
u8 friskedBattler; // Frisk needs to identify 2 battlers in double battles.
bool8 friskedAbility; // If identifies two mons, show the ability pop-up only once.

View File

@ -19,7 +19,7 @@
return score; \
}
u8 ComputeBattleAiScores(u8 battler);
void BattleAI_SetupItems(void);
void BattleAI_SetupFlags(void);
void BattleAI_SetupAIData(u8 defaultScoreMoves);

View File

@ -20,6 +20,7 @@ void ClearBattlerItemEffectHistory(u8 battlerId);
void SaveBattlerData(u8 battlerId);
void SetBattlerData(u8 battlerId);
void RestoreBattlerData(u8 battlerId);
u16 GetAIChosenMove(u8 battlerId);
bool32 WillAIStrikeFirst(void);
u32 GetTotalBaseStat(u32 species);
@ -27,7 +28,7 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler);
bool32 AtMaxHp(u8 battler);
u32 GetHealthPercentage(u8 battler);
bool32 IsBattlerTrapped(u8 battler, bool8 switching);
u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2);
u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2, u16 consideredMove);
bool32 CanTargetFaintAi(u8 battlerDef, u8 battlerAtk);
bool32 CanMoveFaintBattler(u16 move, u8 battlerDef, u8 battlerAtk, u8 nHits);
bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgMod);

View File

@ -81,6 +81,8 @@
#define WILD_DOUBLE_BATTLE ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_TRAINER))))
#define BATTLE_TWO_VS_ONE_OPPONENT ((gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && gTrainerBattleOpponent_B == 0xFFFF))
#define BATTLE_TYPE_HAS_AI (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER)
// Battle Outcome defines
#define B_OUTCOME_WON 1

View File

@ -174,6 +174,7 @@ void BattleAI_SetupFlags(void)
AI_THINKING_STRUCT->aiFlags |= AI_FLAG_DOUBLE_BATTLE; // Act smart in doubles and don't attack your partner.
}
// sBattler_AI set in ComputeBattleAiScores
void BattleAI_SetupAIData(u8 defaultScoreMoves)
{
s32 i, move, dmg;
@ -204,8 +205,9 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves)
AI_THINKING_STRUCT->score[i] = 0;
}
sBattler_AI = gActiveBattler;
//sBattler_AI = gActiveBattler;
gBattlerTarget = SetRandomTarget(sBattler_AI);
gBattleStruct->aiChosenTarget[sBattler_AI] = gBattlerTarget;
}
u8 BattleAI_ChooseMoveOrAction(void)
@ -226,29 +228,52 @@ u8 BattleAI_ChooseMoveOrAction(void)
return ret;
}
// damages/other info computed in GetAIDataAndCalcDmg
u8 ComputeBattleAiScores(u8 battler)
{
sBattler_AI = battler;
BattleAI_SetupAIData(0xF);
return BattleAI_ChooseMoveOrAction();
}
static void SetBattlerAiData(u8 battlerId)
{
AI_DATA->abilities[battlerId] = AI_GetAbility(battlerId);
AI_DATA->items[battlerId] = gBattleMons[battlerId].item;
AI_DATA->holdEffects[battlerId] = AI_GetHoldEffect(battlerId);
AI_DATA->holdEffectParams[battlerId] = GetBattlerHoldEffectParam(battlerId);
AI_DATA->predictedMoves[battlerId] = gLastMoves[battlerId];
AI_DATA->hpPercents[battlerId] = GetHealthPercentage(battlerId);
AI_DATA->moveLimitations[battlerId] = CheckMoveLimitations(battlerId, 0, 0xFF);
}
void GetAiLogicData(void)
{
u32 battlerAtk, battlerDef, i, move;
u8 effectiveness;
s32 dmg;
memset(AI_DATA, 0, sizeof(struct AiLogicData));
if (!(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER)
&& !IsWildMonSmart()))
return;
memset(AI_DATA, 0, sizeof(struct AiLogicData));
for (battlerAtk = 0; battlerAtk < MAX_BATTLERS_COUNT; battlerAtk++)
// get/assume all battler data
for (i = 0; i < gBattlersCount; i++)
{
if (IsBattlerAlive(battlerAtk)) {
AI_DATA->abilities[battlerAtk] = AI_GetAbility(battlerAtk);
AI_DATA->items[battlerAtk] = gBattleMons[battlerAtk].item;
AI_DATA->holdEffects[battlerAtk] = AI_GetHoldEffect(battlerAtk);
AI_DATA->holdEffectParams[battlerAtk] = GetBattlerHoldEffectParam(battlerAtk);
AI_DATA->predictedMoves[battlerAtk] = gLastMoves[battlerAtk];
AI_DATA->hpPercents[battlerAtk] = GetHealthPercentage(battlerAtk);
AI_DATA->moveLimitations[battlerAtk] = CheckMoveLimitations(battlerAtk, 0, MOVE_LIMITATIONS_ALL);
if (IsBattlerAlive(i)) {
SetBattlerAiData(i);
}
}
// Simulate dmg for all AI moves against all other targets
// simulate AI damage
for (battlerAtk = 0; battlerAtk < gBattlersCount; battlerAtk++)
{
if (!IsBattlerAlive(battlerAtk)
|| !IsBattlerAIControlled(battlerAtk)) {
continue;
}
for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
if (battlerAtk == battlerDef)
@ -260,14 +285,12 @@ void GetAiLogicData(void)
dmg = 0;
effectiveness = AI_EFFECTIVENESS_x0;
move = gBattleMons[battlerAtk].moves[i];
if (move != 0
&& move != 0xFFFF
&& gBattleMoves[move].power != 0
&& !(AI_DATA->moveLimitations[battlerAtk] & gBitTable[i]))
{
//&& gBattleMoves[move].power != 0 /* we want to get effectiveness of status moves */
&& !(AI_DATA->moveLimitations[battlerAtk] & gBitTable[i])) {
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness);
if (dmg == 0)
dmg = 1;
}
AI_DATA->simulatedDmg[battlerAtk][battlerDef][i] = dmg;
@ -275,7 +298,6 @@ void GetAiLogicData(void)
}
}
}
}
}
static u8 ChooseMoveOrAction_Singles(void)
@ -495,6 +517,7 @@ static u8 ChooseMoveOrAction_Doubles(void)
}
gBattlerTarget = mostViableTargetsArray[Random() % mostViableTargetsNo];
gBattleStruct->aiChosenTarget[sBattler_AI] = gBattlerTarget;
return actionOrMoveIndex[gBattlerTarget];
}
@ -556,7 +579,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
u16 moveEffect = gBattleMoves[move].effect;
s32 moveType;
u16 moveTarget = AI_GetBattlerMoveTargetType(battlerAtk, move);
u16 accuracy = AI_GetMoveAccuracy(battlerAtk, battlerDef, AI_DATA->atkAbility, AI_DATA->defAbility, AI_DATA->atkHoldEffect, AI_DATA->defHoldEffect, move);
u16 accuracy = AI_GetMoveAccuracy(battlerAtk, battlerDef, move);
u8 effectiveness = AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex];
bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk);
u32 i;
@ -593,7 +616,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
}
// check off screen
if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER)
if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
RETURN_SCORE_MINUS(20); // if target off screen and we go first, don't use move
// check if negates type
@ -1292,7 +1315,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
&& (B_MENTAL_HERB >= GEN_5 && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)
&& !PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, AI_DATA->partnerMove))
{
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker should go first
{
if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF)
score -= 10; // no anticipated move to disable
@ -1312,7 +1335,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
&& (B_MENTAL_HERB >= GEN_5 && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)
&& !DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, AI_DATA->partnerMove))
{
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker should go first
{
if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF)
score -= 10; // no anticipated move to encore
@ -1755,7 +1778,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break;
case EFFECT_SPITE:
case EFFECT_MIMIC:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker should go first
{
if (gLastMoves[battlerDef] == MOVE_NONE
|| gLastMoves[battlerDef] == 0xFFFF)
@ -1912,7 +1935,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (isDoubleBattle)
{
if (IsHazardMoveEffect(gBattleMoves[AI_DATA->partnerMove].effect) // partner is going to set up hazards
&& AI_WhoStrikesFirst(BATTLE_PARTNER(battlerAtk), battlerAtk) == AI_IS_FASTER) // partner is going to set up before the potential Defog
&& AI_WhoStrikesFirst(BATTLE_PARTNER(battlerAtk), battlerAtk, AI_DATA->partnerMove) == AI_IS_FASTER) // partner is going to set up before the potential Defog
{
score -= 10;
break; // Don't use Defog if partner is going to set up hazards
@ -1950,7 +1973,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break;
case EFFECT_SEMI_INVULNERABLE:
if (predictedMove != MOVE_NONE
&& AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER
&& AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER
&& gBattleMoves[predictedMove].effect == EFFECT_SEMI_INVULNERABLE)
score -= 10; // Don't Fly/dig/etc if opponent is going to fly/dig/etc after you
@ -2131,7 +2154,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_ME_FIRST:
if (predictedMove != MOVE_NONE)
{
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER)
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER)
score -= 10; // Target is predicted to go first, Me First will fail
else
return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score);
@ -2320,7 +2343,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
}
break;
case EFFECT_ELECTRIFY:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER
//|| GetMoveTypeSpecial(battlerDef, predictedMove) == TYPE_ELECTRIC // Move will already be electric type
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, AI_DATA->partnerMove))
score -= 10;
@ -2349,7 +2372,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_INSTRUCT:
{
u16 instructedMove;
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER)
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER)
instructedMove = predictedMove;
else
instructedMove = gLastMoves[battlerDef];
@ -2389,21 +2412,21 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break;
case EFFECT_QUASH:
if (!isDoubleBattle
|| AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER
|| AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, AI_DATA->partnerMove))
score -= 10;
break;
case EFFECT_AFTER_YOU:
if (!IsTargetingPartner(battlerAtk, battlerDef)
|| !isDoubleBattle
|| AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER
|| AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, AI_DATA->partnerMove))
score -= 10;
break;
case EFFECT_SUCKER_PUNCH:
if (predictedMove != MOVE_NONE)
{
if (IS_MOVE_STATUS(predictedMove) || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent going first
if (IS_MOVE_STATUS(predictedMove) || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Opponent going first
score -= 10;
}
break;
@ -2455,7 +2478,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score--; // don't want to move last
break;
case EFFECT_FLAIL:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER // Opponent should go first
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER // Opponent should go first
|| AI_DATA->hpPercents[battlerAtk] > 50)
score -= 4;
break;
@ -2604,7 +2627,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
if (AI_DATA->abilities[battlerAtk] == ABILITY_ANGER_POINT)
{
if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner) == AI_IS_SLOWER) // Partner moving first
if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner, move) == AI_IS_SLOWER) // Partner moving first
{
// discourage raising our attack since it's about to be maxed out
if (IsAttackBoostMoveEffect(effect))
@ -2871,7 +2894,7 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_INSTRUCT:
{
u16 instructedMove;
if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner) == AI_IS_FASTER)
if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner, move) == AI_IS_FASTER)
instructedMove = AI_DATA->partnerMove;
else
instructedMove = gLastMoves[battlerAtkPartner];
@ -2885,8 +2908,8 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
}
break;
case EFFECT_AFTER_YOU:
if (AI_WhoStrikesFirst(battlerAtkPartner, FOE(battlerAtkPartner) == AI_IS_SLOWER) // Opponent mon 1 goes before partner
|| AI_WhoStrikesFirst(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)) == AI_IS_SLOWER)) // Opponent mon 2 goes before partner
if (AI_WhoStrikesFirst(battlerAtkPartner, FOE(battlerAtkPartner), AI_DATA->partnerMove) == AI_IS_SLOWER // Opponent mon 1 goes before partner
|| AI_WhoStrikesFirst(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)), AI_DATA->partnerMove) == 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)
break; // These moves need to go last
@ -2913,7 +2936,7 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_MAGNITUDE:
if (!IsBattlerGrounded(battlerAtkPartner)
|| (IsBattlerGrounded(battlerAtkPartner)
&& AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner) == AI_IS_SLOWER
&& AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner, move) == AI_IS_SLOWER
&& IsUngroundingEffect(gBattleMoves[AI_DATA->partnerMove].effect)))
score += 2;
else if (IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_FIRE)
@ -2986,7 +3009,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
// check already dead
if (!IsBattlerIncapacitated(battlerDef, AI_DATA->abilities[battlerDef])
&& CanTargetFaintAi(battlerAtk, battlerDef)
&& AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent should go first
&& AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Opponent should go first
{
if (atkPriority > 0)
score++;
@ -3034,7 +3057,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case ABILITY_GRIM_NEIGH:
case ABILITY_AS_ONE_ICE_RIDER:
case ABILITY_AS_ONE_SHADOW_RIDER:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker should go first
{
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
score += 8; // prioritize killing target for stat boost
@ -3481,7 +3504,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score++;
break;
case EFFECT_MIMIC:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER)
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
{
if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF)
return AI_CheckViability(battlerAtk, battlerDef, gLastMoves[battlerDef], score);
@ -3540,7 +3563,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (gDisableStructs[battlerDef].disableTimer == 0
&& (B_MENTAL_HERB >= GEN_5 && AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)) // mental herb
{
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // AI goes first
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // AI goes first
{
if (gLastMoves[battlerDef] != MOVE_NONE
&& gLastMoves[battlerDef] != 0xFFFF)
@ -3594,7 +3617,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score += 3;
break;
case EFFECT_DESTINY_BOND:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER && CanTargetFaintAi(battlerDef, battlerAtk))
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER && CanTargetFaintAi(battlerDef, battlerAtk))
score += 3;
break;
case EFFECT_SPITE:
@ -3703,7 +3726,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
{
ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score);
}
else if (isDoubleBattle && AI_GetBattlerMoveTargetType(AI_DATA->battlerAtkPartner, AI_DATA->partnerMove) & MOVE_TARGET_FOES_AND_ALLY)
else if (isDoubleBattle && AI_GetBattlerMoveTargetType(BATTLE_PARTNER(battlerAtk), AI_DATA->partnerMove) & MOVE_TARGET_FOES_AND_ALLY)
{
if (AI_DATA->abilities[battlerAtk] != ABILITY_TELEPATHY)
ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score);
@ -3842,7 +3865,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
&& AI_DATA->abilities[battlerAtk] != ABILITY_CONTRARY
&& CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
{
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker goes first
score += 9;
else
score += 3;
@ -3887,7 +3910,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score++;
if (predictedMove != MOVE_NONE && !isDoubleBattle)
{
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker goes first
{
if (gBattleMoves[predictedMove].effect == EFFECT_EXPLOSION
|| gBattleMoves[predictedMove].effect == EFFECT_PROTECT)
@ -3954,7 +3977,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break;
case EFFECT_ATTRACT:
if (!isDoubleBattle && BattlerWillFaintFromSecondaryDamage(battlerDef, AI_DATA->abilities[battlerDef])
&& AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Target goes first
&& AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Target goes first
break; // Don't use if the attract won't have a change to activate
if (gBattleMons[battlerDef].status1 & STATUS1_ANY
@ -3999,7 +4022,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (isDoubleBattle)
{
if (IsHazardMoveEffect(gBattleMoves[AI_DATA->partnerMove].effect) // Partner is going to set up hazards
&& AI_WhoStrikesFirst(battlerAtk, BATTLE_PARTNER(battlerAtk)) == AI_IS_SLOWER) // Partner going first
&& AI_WhoStrikesFirst(battlerAtk, BATTLE_PARTNER(battlerAtk), move) == AI_IS_SLOWER) // Partner going first
break; // Don't use Defog if partner is going to set up hazards
}
@ -4488,13 +4511,13 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score++;
break;
case EFFECT_THROAT_CHOP:
if (predictedMove != MOVE_NONE && TestMoveFlags(predictedMove, FLAG_SOUND) && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER)
if (predictedMove != MOVE_NONE && TestMoveFlags(predictedMove, FLAG_SOUND) && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
score += 3; // Ai goes first and predicts the target will use a sound move
else if (TestMoveFlagsInMoveset(battlerDef, FLAG_SOUND))
score += 3;
break;
case EFFECT_HEAL_BLOCK:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER && predictedMove != MOVE_NONE && IsHealingMoveEffect(gBattleMoves[predictedMove].effect))
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER && predictedMove != MOVE_NONE && IsHealingMoveEffect(gBattleMoves[predictedMove].effect))
score += 3; // Try to cancel healing move
else if (HasHealingEffect(battlerDef) || AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_LEFTOVERS
|| (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_BLACK_SLUDGE && IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON)))
@ -4530,7 +4553,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break;
case EFFECT_QUASH:
if (isDoubleBattle
&& AI_WhoStrikesFirst(BATTLE_PARTNER(battlerAtk), battlerDef) == AI_IS_SLOWER) // Attacker partner wouldn't go before target
&& AI_WhoStrikesFirst(BATTLE_PARTNER(battlerAtk), battlerDef, AI_DATA->partnerMove) == AI_IS_SLOWER) // Attacker partner wouldn't go before target
score++;
break;
case EFFECT_TAILWIND:
@ -4552,7 +4575,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (IsBattlerGrounded(battlerAtk) && HasDamagingMoveOfType(battlerDef, TYPE_ELECTRIC)
&& !(AI_GetTypeEffectiveness(MOVE_EARTHQUAKE, battlerDef, battlerAtk) == AI_EFFECTIVENESS_x0)) // Doesn't resist ground move
{
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker goes first
{
if (gBattleMoves[predictedMove].type == TYPE_GROUND)
score += 3; // Cause the enemy's move to fail
@ -4567,7 +4590,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
}
break;
case EFFECT_CAMOUFLAGE:
if (predictedMove != MOVE_NONE && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER // Attacker goes first
if (predictedMove != MOVE_NONE && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER // Attacker goes first
&& !IS_MOVE_STATUS(move) && AI_GetTypeEffectiveness(predictedMove, battlerDef, battlerAtk) != AI_EFFECTIVENESS_x0)
score++;
break;
@ -4610,7 +4633,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
}
break;
case EFFECT_FLAIL:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Ai goes first
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Ai goes first
{
if (AI_DATA->hpPercents[battlerAtk] < 20)
score++;
@ -4652,7 +4675,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score += 2;
break;
case EFFECT_ENDEAVOR:
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent faster
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Opponent faster
{
if (AI_DATA->hpPercents[battlerAtk] < 40)
score++;
@ -4683,7 +4706,7 @@ static s16 AI_SetupFirstTurn(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
return score;
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING
&& AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER
&& AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER
&& CanTargetFaintAi(battlerDef, battlerAtk)
&& GetMovePriority(battlerAtk, move) == 0)
{

View File

@ -18,6 +18,8 @@
static bool8 HasSuperEffectiveMoveAgainstOpponents(bool8 noRng);
static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent);
static bool8 ShouldUseItem(void);
static bool32 AI_ShouldHeal(u32 healAmount);
static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount);
void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId)
{
@ -788,6 +790,25 @@ static u8 GetAI_ItemType(u16 itemId, const u8 *itemEffect)
return AI_ITEM_NOT_RECOGNIZABLE;
}
static bool32 AiExpectsToFaintPlayer(void)
{
bool32 canFaintPlayer;
u32 i;
u8 target = gBattleStruct->aiChosenTarget[gActiveBattler];
if (gBattleStruct->aiMoveOrAction[gActiveBattler] > 3)
return FALSE; // AI not planning to use move
if (GetBattlerSide(target) != GetBattlerSide(gActiveBattler)
&& CanIndexMoveFaintTarget(gActiveBattler, target, gBattleStruct->aiMoveOrAction[gActiveBattler], 0)
&& AI_WhoStrikesFirst(gActiveBattler, target, GetAIChosenMove(gActiveBattler)) == AI_IS_FASTER) {
// We expect to faint the target and move first -> dont use an item
return TRUE;
}
return FALSE;
}
static bool8 ShouldUseItem(void)
{
struct Pokemon *party;
@ -803,6 +824,9 @@ static bool8 ShouldUseItem(void)
if (gStatuses3[gActiveBattler] & STATUS3_EMBARGO)
return FALSE;
if (AiExpectsToFaintPlayer())
return FALSE;
if (GetBattlerSide(gActiveBattler) == B_SIDE_PLAYER)
party = gPlayerParty;
else
@ -843,21 +867,10 @@ static bool8 ShouldUseItem(void)
switch (*(gBattleStruct->AI_itemType + gActiveBattler / 2))
{
case AI_ITEM_FULL_RESTORE:
if (gBattleMons[gActiveBattler].hp >= gBattleMons[gActiveBattler].maxHP / 4)
break;
if (gBattleMons[gActiveBattler].hp == 0)
break;
shouldUse = TRUE;
shouldUse = AI_ShouldHeal(0);
break;
case AI_ITEM_HEAL_HP:
paramOffset = 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;
shouldUse = AI_ShouldHeal(itemEffects[GetItemEffectParamOffset(item, 4, 4)]);
break;
case AI_ITEM_CURE_CONDITION:
*(gBattleStruct->AI_itemFlags + gActiveBattler / 2) = 0;
@ -948,3 +961,32 @@ static bool8 ShouldUseItem(void)
return FALSE;
}
static bool32 AI_ShouldHeal(u32 healAmount)
{
bool32 shouldHeal = FALSE;
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 = !AI_OpponentCanFaintAiWithMod(healAmount); // if target can kill us even after we heal, why bother
}
return shouldHeal;
}
static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount)
{
u32 i;
// 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
return TRUE;
}
}
}
return FALSE;
}

View File

@ -440,9 +440,14 @@ static const u16 sOtherMoveCallingMoves[] =
};
// Functions
u16 GetAIChosenMove(u8 battlerId)
{
return (gBattleMons[battlerId].moves[gBattleStruct->aiMoveOrAction[battlerId]]);
}
bool32 WillAIStrikeFirst(void)
{
return (AI_WhoStrikesFirst(sBattler_AI, gBattlerTarget) == AI_IS_FASTER);
return (AI_WhoStrikesFirst(sBattler_AI, gBattlerTarget, AI_THINKING_STRUCT->moveConsidered) == AI_IS_FASTER);
}
bool32 AI_RandLessThan(u8 val)
@ -638,7 +643,7 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler)
u32 move = gBattleResources->battleHistory->usedMoves[opposingBattler][i];
if (gBattleMoves[move].effect == EFFECT_PROTECT && move != MOVE_ENDURE)
return TRUE;
if (gBattleMoves[move].effect == EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAI, opposingBattler) == AI_IS_SLOWER)
if (gBattleMoves[move].effect == EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAI, opposingBattler, GetAIChosenMove(battlerAI)) == AI_IS_SLOWER)
return TRUE;
}
return FALSE;
@ -987,23 +992,25 @@ static u8 AI_GetEffectiveness(u16 multiplier)
* AI_IS_FASTER: is user(ai) faster
* AI_IS_SLOWER: is target faster
*/
u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2)
u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2, u16 moveConsidered)
{
u32 fasterAI = 0, fasterPlayer = 0, i;
s8 prioAI = 0;
s8 prioPlayer = 0;
s8 prioBattler2 = 0;
u16 *battler2Moves = GetMovesArray(battler2);
// Check move priorities first.
prioAI = GetMovePriority(battlerAI, AI_THINKING_STRUCT->moveConsidered);
prioAI = GetMovePriority(battlerAI, moveConsidered);
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gBattleMons[battler2].moves[i] == 0 || gBattleMons[battler2].moves[i] == 0xFFFF)
if (battler2Moves[i] == 0 || battler2Moves[i] == 0xFFFF)
continue;
prioPlayer = GetMovePriority(battler2, gBattleMons[battler2].moves[i]);
if (prioAI > prioPlayer)
prioBattler2 = GetMovePriority(battler2, battler2Moves[i]);
if (prioAI > prioBattler2)
fasterAI++;
else if (prioPlayer > prioAI)
else if (prioBattler2 > prioAI)
fasterPlayer++;
}
@ -1017,6 +1024,8 @@ u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2)
}
else
{
if (prioAI > prioBattler2)
return AI_IS_FASTER; // if we didn't know any of battler 2's moves to compare priorities, assume they don't have a prio+ move
// Priorities are the same(at least comparing to moves the AI is aware of), decide by speed.
if (GetWhoStrikesFirst(battlerAI, battler2, TRUE) == 0)
return AI_IS_FASTER;
@ -2422,7 +2431,7 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo
/*if (IsPredictedToSwitch(battlerDef, battlerAtk) && !hasStatBoost)
return PIVOT; // Try pivoting so you can switch to a better matchup to counter your new opponent*/
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker goes first
{
if (!CanAIFaintTarget(battlerAtk, battlerDef, 0)) // Can't KO foe otherwise
{
@ -2782,7 +2791,7 @@ u32 ShouldTryToFlinch(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbili
{
if (defAbility == ABILITY_INNER_FOCUS
|| DoesSubstituteBlockMove(battlerAtk, battlerDef, move)
|| AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent goes first
|| AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Opponent goes first
{
return 0; // don't try to flinch
}
@ -2906,7 +2915,7 @@ bool32 ShouldUseRecoilMove(u8 battlerAtk, u8 battlerDef, u32 recoilDmg, u8 moveI
bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage)
{
if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER)
if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
{
// using item or user goes first
u8 healPercent = (gBattleMoves[move].argument == 0) ? 50 : gBattleMoves[move].argument;
@ -2933,7 +2942,7 @@ bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage)
bool32 ShouldRecover(u8 battlerAtk, u8 battlerDef, u16 move, u8 healPercent)
{
if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER)
if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
{
// using item or user going first
s32 damage = AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex];

View File

@ -1535,7 +1535,7 @@ static void OpponentHandlePrintSelectionString(void)
static void OpponentHandleChooseAction(void)
{
AI_TrySwitchOrUseItem(); // TODO consider move choice first
AI_TrySwitchOrUseItem();
OpponentBufferExecCompleted();
}
@ -1559,9 +1559,8 @@ static void OpponentHandleChooseMove(void)
if (gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER)
|| IsWildMonSmart())
{
BattleAI_SetupAIData(0xF);
chosenMoveId = BattleAI_ChooseMoveOrAction();
chosenMoveId = gBattleStruct->aiMoveOrAction[gActiveBattler];
gBattlerTarget = gBattleStruct->aiChosenTarget[gActiveBattler];
switch (chosenMoveId)
{
case AI_CHOICE_WATCH:

View File

@ -1515,8 +1515,8 @@ static void PlayerPartnerHandleChooseMove(void)
u8 chosenMoveId;
struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct*)(&gBattleResources->bufferA[gActiveBattler][4]);
BattleAI_SetupAIData(0xF);
chosenMoveId = BattleAI_ChooseMoveOrAction();
chosenMoveId = gBattleStruct->aiMoveOrAction[gActiveBattler];
gBattlerTarget = gBattleStruct->aiChosenTarget[gActiveBattler];
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED))
gBattlerTarget = gActiveBattler;

View File

@ -3918,6 +3918,11 @@ static void HandleTurnActionSelectionState(void)
case STATE_TURN_START_RECORD: // Recorded battle related action on start of every turn.
RecordedBattle_CopyBattlerMoves();
gBattleCommunication[gActiveBattler] = STATE_BEFORE_ACTION_CHOSEN;
// Do AI score computations here so we can use them in AI_TrySwitchOrUseItem
if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart()) && IsBattlerAIControlled(gActiveBattler)) {
gBattleStruct->aiMoveOrAction[gActiveBattler] = ComputeBattleAiScores(gActiveBattler);
}
break;
case STATE_BEFORE_ACTION_CHOSEN: // Choose an action.
*(gBattleStruct->monToSwitchIntoId + gActiveBattler) = PARTY_SIZE;