optimize dmg move result and ai who is faster

This commit is contained in:
DizzyEggg 2023-09-13 13:23:19 +02:00
parent 406209f738
commit 1a64938c9b
9 changed files with 209 additions and 169 deletions

View File

@ -289,6 +289,8 @@ struct AiLogicData
u16 predictedMoves[MAX_BATTLERS_COUNT];
u8 hpPercents[MAX_BATTLERS_COUNT];
u16 partnerMove;
u16 speedStats[MAX_BATTLERS_COUNT]; // Speed stats for all battles, calculated only once, same way as damages
u8 moveDmgResult[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // MOVE_POWER defines for GetMoveDamageResult ; attacker, target, moveIndex
s32 simulatedDmg[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
u8 effectiveness[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
u8 moveLimitations[MAX_BATTLERS_COUNT];

View File

@ -7,6 +7,8 @@
#define FOE(battler) ((BATTLE_OPPOSITE(battler)) & BIT_SIDE)
#define AI_STRIKES_FIRST(battlerAi, battlerDef, move)((AI_WhoStrikesFirst(battlerAi, battlerDef, move) == AI_IS_FASTER))
bool32 AI_RandLessThan(u8 val);
bool32 IsAiVsAiBattle(void);
bool32 BattlerHasAi(u32 battlerId);
@ -23,13 +25,12 @@ void SetBattlerData(u32 battlerId);
void RestoreBattlerData(u32 battlerId);
u16 GetAIChosenMove(u8 battlerId);
bool32 WillAIStrikeFirst(void);
u32 GetTotalBaseStat(u32 species);
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, u16 consideredMove);
u32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 moveConsidered);
bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk);
bool32 CanMoveFaintBattler(u16 move, u32 battlerDef, u32 battlerAtk, u8 nHits);
bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dmgMod);
@ -87,7 +88,8 @@ bool32 MovesWithSplitUnusable(u32 attacker, u32 target, u32 split);
s32 AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower);
s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower);
u32 GetNoOfHitsToKO(u32 dmg, s32 hp);
u8 GetMoveDamageResult(u16 move);
void SetMoveDamageResult(u32 battlerAtk, u16 *moves);
u32 GetMoveDamageResult(u32 battlerAtk, u32 battlerDef, u32 moveIndex);
u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef);
uq4_12_t AI_GetTypeEffectiveness(u16 move, u32 battlerAtk, u32 battlerDef);
u32 AI_GetMoveEffectiveness(u16 move, u32 battlerAtk, u32 battlerDef);

View File

@ -59,10 +59,13 @@ void BattleTurnPassed(void);
u8 IsRunningFromBattleImpossible(u32 battler);
void SwitchPartyOrder(u8 battlerId);
void SwapTurnOrder(u8 id1, u8 id2);
u32 GetBattlerTotalSpeedStat(u8 battlerId);
u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, u32 holdEffect);
u32 GetBattlerTotalSpeedStat(u32 battler);
s8 GetChosenMovePriority(u32 battlerId);
s8 GetMovePriority(u32 battlerId, u16 move);
u8 GetWhoStrikesFirst(u8 battlerId1, u8 battlerId2, bool8 ignoreChosenMoves);
u32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMoves, u32 ability1, u32 ability2,
u32 holdEffectBattler1, u32 holdEffectBattler2, u32 speedBattler1, u32 speedBattler2, s32 priority1, s32 priority2);
u32 GetWhichBattlerFaster(u32 battler1, u32 battler2, bool32 ignoreChosenMoves);
void RunBattleScriptCommands_PopCallbacksStack(void);
void RunBattleScriptCommands(void);
void SpecialStatusesClear(void);

View File

@ -167,7 +167,7 @@ u32 GetBattlerHoldEffectParam(u32 battler);
bool32 IsMoveMakingContact(u32 move, u32 battlerAtk);
bool32 IsBattlerGrounded(u32 battler);
bool32 IsBattlerAlive(u32 battler);
u32 GetBattleMonMoveSlot(struct BattlePokemon *battleMon, u32 move);
u32 GetMoveSlot(u16 *moves, u32 move);
u32 GetBattlerWeight(u32 battler);
u32 CalcRolloutBasePower(u32 battlerAtk, u32 basePower, u32 rolloutTimer);
u32 CalcFuryCutterBasePower(u32 basePower, u32 furyCutterCounter);

View File

@ -338,18 +338,55 @@ void Ai_UpdateFaintData(u32 battler)
static void SetBattlerAiData(u32 battler)
{
AI_DATA->abilities[battler] = AI_GetAbility(battler);
u32 ability, holdEffect;
ability = AI_DATA->abilities[battler] = AI_GetAbility(battler);
AI_DATA->items[battler] = gBattleMons[battler].item;
AI_DATA->holdEffects[battler] = AI_GetHoldEffect(battler);
holdEffect = AI_DATA->holdEffects[battler] = AI_GetHoldEffect(battler);
AI_DATA->holdEffectParams[battler] = GetBattlerHoldEffectParam(battler);
AI_DATA->predictedMoves[battler] = gLastMoves[battler];
AI_DATA->hpPercents[battler] = GetHealthPercentage(battler);
AI_DATA->moveLimitations[battler] = CheckMoveLimitations(battler, 0, MOVE_LIMITATIONS_ALL);
AI_DATA->speedStats[battler] = GetBattlerTotalSpeedStatArgs(battler, ability, holdEffect);
}
static void SetBattlerAiMovesData(u32 battlerAtk, u32 battlersCount)
{
u32 battlerDef, i;
u16 *moves;
// Simulate dmg for both ai controlled mons and for player controlled mons.
SaveBattlerData(battlerAtk);
moves = GetMovesArray(battlerAtk);
for (battlerDef = 0; battlerDef < battlersCount; battlerDef++)
{
if (battlerAtk == battlerDef)
continue;
SaveBattlerData(battlerDef);
for (i = 0; i < MAX_MON_MOVES; i++)
{
s32 dmg = 0;
u8 effectiveness = AI_EFFECTIVENESS_x0;
u32 move = moves[i];
if (move != 0
&& move != 0xFFFF
//&& 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, TRUE);
}
AI_DATA->simulatedDmg[battlerAtk][battlerDef][i] = dmg;
AI_DATA->effectiveness[battlerAtk][battlerDef][i] = effectiveness;
}
}
SetMoveDamageResult(battlerAtk, moves);
}
void SetAiLogicDataForTurn(void)
{
u32 battlerAtk, battlerDef, i, battlersCount;
u32 battlerAtk, battlersCount;
memset(AI_DATA, 0, sizeof(struct AiLogicData));
@ -366,33 +403,7 @@ void SetAiLogicDataForTurn(void)
continue;
SetBattlerAiData(battlerAtk);
if (!IsAiBattlerAware(battlerAtk))
continue;
SaveBattlerData(battlerAtk);
for (battlerDef = 0; battlerDef < battlersCount; battlerDef++)
{
if (battlerAtk == battlerDef)
continue;
SaveBattlerData(battlerDef);
for (i = 0; i < MAX_MON_MOVES; i++)
{
s32 dmg = 0;
u8 effectiveness = AI_EFFECTIVENESS_x0;
u32 move = gBattleMons[battlerAtk].moves[i];
if (move != 0
&& move != 0xFFFF
//&& 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, TRUE);
}
AI_DATA->simulatedDmg[battlerAtk][battlerDef][i] = dmg;
AI_DATA->effectiveness[battlerAtk][battlerDef][i] = effectiveness;
}
}
SetBattlerAiMovesData(battlerAtk, battlersCount);
}
}
@ -715,6 +726,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u16 move, s32 score)
u32 i;
u16 predictedMove = AI_DATA->predictedMoves[battlerDef];
SetTypeBeforeUsingMove(move, battlerAtk);
GET_MOVE_TYPE(move, moveType);
@ -2616,7 +2628,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u16 move, s32 score)
score--;
break;
case EFFECT_VITAL_THROW:
if (WillAIStrikeFirst() && AI_DATA->hpPercents[battlerAtk] < 40)
if (AI_STRIKES_FIRST(battlerAtk, battlerDef, move) && AI_DATA->hpPercents[battlerAtk] < 40)
score--; // don't want to move last
break;
case EFFECT_FLAIL:
@ -2688,16 +2700,20 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u16 move, s32 score)
static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u16 move, s32 score)
{
u32 movesetIndex = AI_THINKING_STRUCT->movesetIndex;
bool32 aiFaster;
if (IsTargetingPartner(battlerAtk, battlerDef))
return score;
if (gBattleMoves[move].power == 0)
return score; // can't make anything faint with no power
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0) && gBattleMoves[move].effect != EFFECT_EXPLOSION)
aiFaster = AI_STRIKES_FIRST(battlerAtk, battlerDef, move);
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, 0) && gBattleMoves[move].effect != EFFECT_EXPLOSION)
{
// this move can faint the target
if (WillAIStrikeFirst() || GetMovePriority(battlerAtk, move) > 0)
if (aiFaster || GetMovePriority(battlerAtk, move) > 0)
score += 4; // we go first or we're using priority move
else
score += 2;
@ -2708,10 +2724,10 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u16 move, s32 score)
if (gBattleMoves[move].highCritRatio)
score += 2; // crit makes it more likely to make them faint
if (GetMoveDamageResult(move) == MOVE_POWER_OTHER)
if (GetMoveDamageResult(battlerAtk, battlerDef, movesetIndex) == MOVE_POWER_OTHER)
score--;
switch (AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex])
switch (AI_DATA->effectiveness[battlerAtk][battlerDef][movesetIndex])
{
case AI_EFFECTIVENESS_x8:
score += 8;
@ -2729,9 +2745,9 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u16 move, s32 score)
}
//AI_TryToFaint_CheckIfDanger
if (!WillAIStrikeFirst() && CanTargetFaintAi(battlerDef, battlerAtk))
if (!aiFaster && CanTargetFaintAi(battlerDef, battlerAtk))
{ // AI_TryToFaint_Danger
if (GetMoveDamageResult(move) != MOVE_POWER_BEST)
if (GetMoveDamageResult(battlerAtk, battlerDef, movesetIndex) != MOVE_POWER_BEST)
score--;
else
score++;
@ -2793,7 +2809,6 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u16 move, s32 score)
}
} // check partner move effect
// consider our move effect relative to partner state
switch (effect)
{
@ -2815,7 +2830,6 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u16 move, s32 score)
break;
} // our effect relative to partner
// consider global move effects
switch (effect)
{
@ -2853,11 +2867,10 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u16 move, s32 score)
break;
} // global move effect check
// check specific target
if (IsTargetingPartner(battlerAtk, battlerDef))
{
if (GetMoveDamageResult(move) == MOVE_POWER_OTHER)
if (GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == MOVE_POWER_OTHER)
{
// partner ability checks
if (!partnerProtecting && moveTarget != MOVE_TARGET_BOTH && !DoesBattlerIgnoreAbilityChecks(AI_DATA->abilities[battlerAtk], move))
@ -2900,7 +2913,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u16 move, s32 score)
}
break;
case ABILITY_WATER_COMPACTION:
if (moveType == TYPE_WATER && GetMoveDamageResult(move) == MOVE_POWER_WEAK)
if (moveType == TYPE_WATER && GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == MOVE_POWER_WEAK)
{
RETURN_SCORE_PLUS(1); // only mon with this ability is weak to water so only make it okay if we do very little damage
}
@ -3161,6 +3174,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score
u32 effectiveness = AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex];
u8 atkPriority = GetMovePriority(battlerAtk, move);
u16 predictedMove = AI_DATA->predictedMoves[battlerDef];
u32 predictedMoveSlot = GetMoveSlot(GetMovesArray(battlerDef), predictedMove);
bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk);
u32 i;
// We only check for moves that have a 20% chance or more for their secondary effect to happen because moves with a smaller chance are rather worthless. We don't want the AI to use those.
@ -3203,7 +3217,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score
}
// check damage
if (gBattleMoves[move].power != 0 && GetMoveDamageResult(move) == MOVE_POWER_WEAK)
if (gBattleMoves[move].power != 0 && GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == MOVE_POWER_WEAK)
score--;
// check status move preference
@ -3334,7 +3348,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score
break;
case EFFECT_SPEED_UP:
case EFFECT_SPEED_UP_2:
if (!WillAIStrikeFirst())
if (!AI_STRIKES_FIRST(battlerAtk, battlerDef, move))
{
if (!AI_RandLessThan(70))
score += 3;
@ -3432,7 +3446,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score
break;
case EFFECT_SPEED_DOWN:
case EFFECT_SPEED_DOWN_2:
if (WillAIStrikeFirst())
if (AI_STRIKES_FIRST(battlerAtk, battlerDef, move))
score -= 3;
else if (!AI_RandLessThan(70))
score += 2;
@ -3675,7 +3689,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score
score += 2;
break;
case EFFECT_SPEED_DOWN_HIT:
if (WillAIStrikeFirst())
if (AI_STRIKES_FIRST(battlerAtk, battlerDef, move))
score -= 2;
else if (!AI_RandLessThan(70))
score++;
@ -3819,7 +3833,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score
score++;
break;
case EFFECT_SPEED_UP_HIT:
if (sereneGraceBoost && AI_DATA->abilities[battlerDef] != ABILITY_CONTRARY && !WillAIStrikeFirst())
if (sereneGraceBoost && AI_DATA->abilities[battlerDef] != ABILITY_CONTRARY && !AI_STRIKES_FIRST(battlerAtk, battlerDef, move))
score += 3;
break;
case EFFECT_DESTINY_BOND:
@ -4432,7 +4446,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score
if (IsStatBoostingBerry(item) && AI_DATA->hpPercents[battlerAtk] > 60)
score++;
else if (ShouldRestoreHpBerry(battlerAtk, item) && !CanAIFaintTarget(battlerAtk, battlerDef, 0)
&& ((GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 && CanTargetFaintAiWithMod(battlerDef, battlerAtk, 0, 0))
&& ((GetWhichBattlerFaster(battlerAtk, battlerDef, TRUE) == 0 && CanTargetFaintAiWithMod(battlerDef, battlerAtk, 0, 0))
|| !CanTargetFaintAiWithMod(battlerDef, battlerAtk, toHeal, 0)))
score++; // Recycle healing berry if we can't otherwise faint the target and the target wont kill us after we activate the berry
}
@ -4865,7 +4879,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score
{
if (gDisableStructs[battlerDef].tauntTimer != 0)
score++; // target must use damaging move
if (GetMoveDamageResult(predictedMove) >= MOVE_POWER_GOOD && GetBattleMoveSplit(predictedMove) == SPLIT_PHYSICAL)
if (GetMoveDamageResult(battlerDef, battlerAtk, predictedMoveSlot) >= MOVE_POWER_GOOD && GetBattleMoveSplit(predictedMove) == SPLIT_PHYSICAL)
score += 3;
}
break;
@ -4874,7 +4888,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u16 move, s32 score
{
if (gDisableStructs[battlerDef].tauntTimer != 0)
score++; // target must use damaging move
if (GetMoveDamageResult(predictedMove) >= MOVE_POWER_GOOD && GetBattleMoveSplit(predictedMove) == SPLIT_SPECIAL)
if (GetMoveDamageResult(battlerDef, battlerAtk, predictedMoveSlot) >= MOVE_POWER_GOOD && GetBattleMoveSplit(predictedMove) == SPLIT_SPECIAL)
score += 3;
}
break;
@ -5111,7 +5125,7 @@ static s32 AI_PreferStrongestMove(u32 battlerAtk, u32 battlerDef, u16 move, s32
if (IsTargetingPartner(battlerAtk, battlerDef))
return score;
if (GetMoveDamageResult(move) == MOVE_POWER_BEST)
if (GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == MOVE_POWER_BEST)
score += 2;
return score;
@ -5124,7 +5138,7 @@ static s32 AI_PreferBatonPass(u32 battlerAtk, u32 battlerDef, u16 move, s32 scor
if (IsTargetingPartner(battlerAtk, battlerDef)
|| CountUsablePartyMons(battlerAtk) == 0
|| GetMoveDamageResult(move) != MOVE_POWER_OTHER
|| GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) != MOVE_POWER_OTHER
|| !HasMoveEffect(battlerAtk, EFFECT_BATON_PASS)
|| IsBattlerTrapped(battlerAtk, TRUE))
return score;

View File

@ -400,7 +400,7 @@ static bool8 ShouldSwitchIfGameStatePrompt(u32 battler)
&& AnyStatIsRaised(battler))
switchMon = FALSE;
if (AiExpectsToFaintPlayer(battler)
&& !WillAIStrikeFirst()
&& !AI_STRIKES_FIRST(battler, opposingBattler, 0)
&& !AI_OpponentCanFaintAiWithMod(battler, 0))
switchMon = FALSE;
}

View File

@ -424,11 +424,6 @@ u16 GetAIChosenMove(u8 battlerId)
return (gBattleMons[battlerId].moves[gBattleStruct->aiMoveOrAction[battlerId]]);
}
bool32 WillAIStrikeFirst(void)
{
return (AI_WhoStrikesFirst(sBattler_AI, gBattlerTarget, AI_THINKING_STRUCT->moveConsidered) == AI_IS_FASTER);
}
bool32 AI_RandLessThan(u8 val)
{
if ((Random() % 0xFF) < val)
@ -945,85 +940,91 @@ u32 GetNoOfHitsToKO(u32 dmg, s32 hp)
return hp / (dmg + 1) + 1;
}
u8 GetMoveDamageResult(u16 move)
void SetMoveDamageResult(u32 battlerAtk, u16 *moves)
{
s32 i, checkedMove, bestId, currId, hp;
s32 i, j, battlerDef, bestId, currId, hp, result;
s32 moveDmgs[MAX_MON_MOVES];
u8 result;
bool8 isNotConsidered[MAX_MON_MOVES];
for (i = 0; sIgnoredPowerfulMoveEffects[i] != IGNORED_MOVES_END; i++)
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gBattleMoves[move].effect == sIgnoredPowerfulMoveEffects[i])
break;
}
if (gBattleMoves[move].power != 0 && sIgnoredPowerfulMoveEffects[i] == IGNORED_MOVES_END)
{
// Considered move has power and is not in sIgnoredPowerfulMoveEffects
// Check all other moves and calculate their power
for (checkedMove = 0; checkedMove < MAX_MON_MOVES; checkedMove++)
u16 move = moves[i];
for (j = 0; sIgnoredPowerfulMoveEffects[j] != IGNORED_MOVES_END; j++)
{
for (i = 0; sIgnoredPowerfulMoveEffects[i] != IGNORED_MOVES_END; i++)
{
if (gBattleMoves[gBattleMons[sBattler_AI].moves[checkedMove]].effect == sIgnoredPowerfulMoveEffects[i])
break;
}
if (gBattleMoves[move].effect == sIgnoredPowerfulMoveEffects[j])
break;
}
if (move == 0 || move == 0xFFFF || gBattleMoves[move].power == 0 || sIgnoredPowerfulMoveEffects[j] != IGNORED_MOVES_END)
isNotConsidered[i] = TRUE;
else
isNotConsidered[i] = FALSE;
if (gBattleMons[sBattler_AI].moves[checkedMove] != MOVE_NONE
&& sIgnoredPowerfulMoveEffects[i] == IGNORED_MOVES_END
&& gBattleMoves[gBattleMons[sBattler_AI].moves[checkedMove]].power != 0)
for (battlerDef = 0; battlerDef < MAX_BATTLERS_COUNT; battlerDef++)
{
if (battlerDef == battlerAtk)
continue;
if (isNotConsidered[i])
{
moveDmgs[checkedMove] = AI_DATA->simulatedDmg[sBattler_AI][gBattlerTarget][checkedMove];
AI_DATA->moveDmgResult[battlerAtk][battlerDef][i] = MOVE_POWER_OTHER; // Move has a power of 0/1, or is in the group sIgnoredPowerfulMoveEffects
}
else
{
moveDmgs[checkedMove] = 0;
}
}
hp = gBattleMons[gBattlerTarget].hp + (20 * gBattleMons[gBattlerTarget].hp / 100); // 20 % add to make sure the battler is always fainted
// If a move can faint battler, it doesn't matter how much damage it does
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moveDmgs[i] > hp)
moveDmgs[i] = hp;
}
for (bestId = 0, i = 1; i < MAX_MON_MOVES; i++)
{
if (moveDmgs[i] > moveDmgs[bestId])
bestId = i;
if (moveDmgs[i] == moveDmgs[bestId])
{
switch (WhichMoveBetter(gBattleMons[sBattler_AI].moves[bestId], gBattleMons[sBattler_AI].moves[i]))
// Considered move has power and is not in sIgnoredPowerfulMoveEffects
// Check all other moves and calculate their power
for (j = 0; j < MAX_MON_MOVES; j++)
{
case 2:
if (Random() & 1)
break;
case 1:
bestId = i;
break;
if (!isNotConsidered[j])
moveDmgs[j] = AI_DATA->simulatedDmg[battlerAtk][battlerDef][j];
else
moveDmgs[j] = 0;
}
hp = gBattleMons[battlerDef].hp + (20 * gBattleMons[battlerDef].hp / 100); // 20 % add to make sure the battler is always fainted
// If a move can faint battler, it doesn't matter how much damage it does
for (j = 0; j < MAX_MON_MOVES; j++)
{
if (moveDmgs[j] > hp)
moveDmgs[j] = hp;
}
for (bestId = 0, j = 1; j < MAX_MON_MOVES; j++)
{
if (moveDmgs[j] > moveDmgs[bestId])
bestId = j;
if (moveDmgs[j] == moveDmgs[bestId])
{
switch (WhichMoveBetter(gBattleMons[battlerAtk].moves[bestId], gBattleMons[battlerAtk].moves[j]))
{
case 2:
if (Random() & 1)
break;
case 1:
bestId = j;
break;
}
}
}
currId = i;
if (currId == bestId)
result = MOVE_POWER_BEST;
else if ((moveDmgs[currId] >= hp || moveDmgs[bestId] < hp) // If current move can faint as well, or if neither can
&& GetNoOfHitsToKO(moveDmgs[currId], hp) - GetNoOfHitsToKO(moveDmgs[bestId], hp) <= 2 // Consider a move weak if it needs to be used at least 2 times more to faint the target, compared to the best move.
&& WhichMoveBetter(gBattleMons[battlerAtk].moves[bestId], gBattleMons[battlerAtk].moves[currId]) != 0)
result = MOVE_POWER_GOOD;
else
result = MOVE_POWER_WEAK;
AI_DATA->moveDmgResult[battlerAtk][battlerDef][i] = result;
}
}
currId = AI_THINKING_STRUCT->movesetIndex;
if (currId == bestId)
AI_THINKING_STRUCT->funcResult = MOVE_POWER_BEST;
else if ((moveDmgs[currId] >= hp || moveDmgs[bestId] < hp) // If current move can faint as well, or if neither can
&& GetNoOfHitsToKO(moveDmgs[currId], hp) - GetNoOfHitsToKO(moveDmgs[bestId], hp) <= 2 // Consider a move weak if it needs to be used at least 2 times more to faint the target, compared to the best move.
&& WhichMoveBetter(gBattleMons[sBattler_AI].moves[bestId], gBattleMons[sBattler_AI].moves[currId]) != 0)
AI_THINKING_STRUCT->funcResult = MOVE_POWER_GOOD;
else
AI_THINKING_STRUCT->funcResult = MOVE_POWER_WEAK;
}
else
{
// Move has a power of 0/1, or is in the group sIgnoredPowerfulMoveEffects
AI_THINKING_STRUCT->funcResult = MOVE_POWER_OTHER;
}
}
return AI_THINKING_STRUCT->funcResult;
u32 GetMoveDamageResult(u32 battlerAtk, u32 battlerDef, u32 moveIndex)
{
return AI_DATA->moveDmgResult[battlerAtk][battlerDef][moveIndex];
}
u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef)
@ -1090,7 +1091,7 @@ static u32 AI_GetEffectiveness(uq4_12_t multiplier)
* AI_IS_FASTER: is user(ai) faster
* AI_IS_SLOWER: is target faster
*/
u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2, u16 moveConsidered)
u32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 moveConsidered)
{
u32 fasterAI = 0, fasterPlayer = 0, i;
s8 prioAI = 0;
@ -1126,7 +1127,11 @@ u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2, u16 moveConsidered)
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)
if (GetWhichBattlerFasterArgs(battlerAI, battler2, TRUE,
AI_DATA->abilities[battlerAI], AI_DATA->abilities[battler2],
AI_DATA->holdEffects[battlerAI], AI_DATA->holdEffects[battler2],
AI_DATA->speedStats[battlerAI], AI_DATA->speedStats[battler2],
prioAI, prioBattler2) == 0)
return AI_IS_FASTER;
else
return AI_IS_SLOWER;
@ -1795,7 +1800,7 @@ u32 CountNegativeStatStages(u8 battlerId)
bool32 ShouldLowerAttack(u32 battlerAtk, u32 battlerDef, u16 defAbility)
{
if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_ATK] > 4
@ -1812,7 +1817,7 @@ bool32 ShouldLowerAttack(u32 battlerAtk, u32 battlerDef, u16 defAbility)
bool32 ShouldLowerDefense(u32 battlerAtk, u32 battlerDef, u16 defAbility)
{
if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_DEF] > 4
@ -1829,10 +1834,10 @@ bool32 ShouldLowerDefense(u32 battlerAtk, u32 battlerDef, u16 defAbility)
bool32 ShouldLowerSpeed(u32 battlerAtk, u32 battlerDef, u16 defAbility)
{
if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return FALSE; // Don't bother lowering stats if can kill enemy.
if (!WillAIStrikeFirst()
if (!AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered)
&& defAbility != ABILITY_CONTRARY
&& defAbility != ABILITY_CLEAR_BODY
&& defAbility != ABILITY_FULL_METAL_BODY
@ -1844,7 +1849,7 @@ bool32 ShouldLowerSpeed(u32 battlerAtk, u32 battlerDef, u16 defAbility)
bool32 ShouldLowerSpAtk(u32 battlerAtk, u32 battlerDef, u16 defAbility)
{
if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_SPATK] > 4
@ -1860,7 +1865,7 @@ bool32 ShouldLowerSpAtk(u32 battlerAtk, u32 battlerDef, u16 defAbility)
bool32 ShouldLowerSpDef(u32 battlerAtk, u32 battlerDef, u16 defAbility)
{
if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_SPDEF] > 4
@ -1876,7 +1881,7 @@ bool32 ShouldLowerSpDef(u32 battlerAtk, u32 battlerDef, u16 defAbility)
bool32 ShouldLowerAccuracy(u32 battlerAtk, u32 battlerDef, u16 defAbility)
{
if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return FALSE; // Don't bother lowering stats if can kill enemy.
if (defAbility != ABILITY_CONTRARY
@ -1891,7 +1896,7 @@ bool32 ShouldLowerAccuracy(u32 battlerAtk, u32 battlerDef, u16 defAbility)
bool32 ShouldLowerEvasion(u32 battlerAtk, u32 battlerDef, u16 defAbility)
{
if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
if (AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return FALSE; // Don't bother lowering stats if can kill enemy.
if (gBattleMons[battlerDef].statStages[STAT_EVASION] > DEFAULT_STAT_STAGE
@ -3637,7 +3642,7 @@ void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u8 statId, s32 *score)
}
break;
case STAT_SPEED:
if (!WillAIStrikeFirst())
if (!AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered))
{
if (gBattleMons[battlerAtk].statStages[STAT_SPEED] < STAT_UP_2_STAGE)
*score += 2;

View File

@ -3771,7 +3771,7 @@ static void TryDoEventsBeforeFirstTurn(void)
{
for (j = i + 1; j < gBattlersCount; j++)
{
if (GetWhoStrikesFirst(gBattlerByTurnOrder[i], gBattlerByTurnOrder[j], TRUE) != 0)
if (GetWhichBattlerFaster(gBattlerByTurnOrder[i], gBattlerByTurnOrder[j], TRUE) != 0)
SwapTurnOrder(i, j);
}
}
@ -4598,11 +4598,10 @@ void SwapTurnOrder(u8 id1, u8 id2)
SWAP(gBattlerByTurnOrder[id1], gBattlerByTurnOrder[id2], temp);
}
u32 GetBattlerTotalSpeedStat(u8 battler)
// For AI, so it doesn't 'cheat' by knowing player's ability
u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, u32 holdEffect)
{
u32 speed = gBattleMons[battler].speed;
u32 ability = GetBattlerAbility(battler);
u32 holdEffect = GetBattlerHoldEffect(battler, TRUE);
u32 highestStat = GetHighestStatId(battler);
// weather abilities
@ -4669,6 +4668,13 @@ u32 GetBattlerTotalSpeedStat(u8 battler)
return speed;
}
u32 GetBattlerTotalSpeedStat(u32 battler)
{
u32 ability = GetBattlerAbility(battler);
u32 holdEffect = GetBattlerHoldEffect(battler, TRUE);
return GetBattlerTotalSpeedStatArgs(battler, ability, holdEffect);
}
s8 GetChosenMovePriority(u32 battler)
{
u16 move;
@ -4733,17 +4739,13 @@ s8 GetMovePriority(u32 battler, u16 move)
return priority;
}
u8 GetWhoStrikesFirst(u8 battler1, u8 battler2, bool8 ignoreChosenMoves)
// Function for AI with variables provided as arguments to speed the computation time
u32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMoves, u32 ability1, u32 ability2,
u32 holdEffectBattler1, u32 holdEffectBattler2, u32 speedBattler1, u32 speedBattler2, s32 priority1, s32 priority2)
{
u8 strikesFirst = 0;
u32 speedBattler1 = 0, speedBattler2 = 0;
u32 holdEffectBattler1 = 0, holdEffectBattler2 = 0;
s8 priority1 = 0, priority2 = 0;
u16 ability1 = GetBattlerAbility(battler1), ability2 = GetBattlerAbility(battler2);
u32 strikesFirst = 0;
// Battler 1
speedBattler1 = GetBattlerTotalSpeedStat(battler1);
holdEffectBattler1 = GetBattlerHoldEffect(battler1, TRUE);
// Quick Draw
if (!ignoreChosenMoves && ability1 == ABILITY_QUICK_DRAW && !IS_MOVE_STATUS(gChosenMoveByBattler[battler1]) && Random() % 100 < 30)
gProtectStructs[battler1].quickDraw = TRUE;
@ -4754,8 +4756,6 @@ u8 GetWhoStrikesFirst(u8 battler1, u8 battler2, bool8 ignoreChosenMoves)
gProtectStructs[battler1].usedCustapBerry = TRUE;
// Battler 2
speedBattler2 = GetBattlerTotalSpeedStat(battler2);
holdEffectBattler2 = GetBattlerHoldEffect(battler2, TRUE);
// Quick Draw
if (!ignoreChosenMoves && ability2 == ABILITY_QUICK_DRAW && !IS_MOVE_STATUS(gChosenMoveByBattler[battler2]) && Random() % 100 < 30)
gProtectStructs[battler2].quickDraw = TRUE;
@ -4765,14 +4765,6 @@ u8 GetWhoStrikesFirst(u8 battler1, u8 battler2, bool8 ignoreChosenMoves)
|| (holdEffectBattler2 == HOLD_EFFECT_CUSTAP_BERRY && HasEnoughHpToEatBerry(battler2, 4, gBattleMons[battler2].item))))
gProtectStructs[battler2].usedCustapBerry = TRUE;
if (!ignoreChosenMoves)
{
if (gChosenActionByBattler[battler1] == B_ACTION_USE_MOVE)
priority1 = GetChosenMovePriority(battler1);
if (gChosenActionByBattler[battler2] == B_ACTION_USE_MOVE)
priority2 = GetChosenMovePriority(battler2);
}
if (priority1 == priority2)
{
// QUICK CLAW / CUSTAP - always first
@ -4835,6 +4827,28 @@ u8 GetWhoStrikesFirst(u8 battler1, u8 battler2, bool8 ignoreChosenMoves)
return strikesFirst;
}
u32 GetWhichBattlerFaster(u32 battler1, u32 battler2, bool32 ignoreChosenMoves)
{
s32 priority1 = 0, priority2 = 0;
u32 ability1 = GetBattlerAbility(battler1);
u32 speedBattler1 = GetBattlerTotalSpeedStat(battler1);
u32 holdEffectBattler1 = GetBattlerHoldEffect(battler1, TRUE);
u32 speedBattler2 = GetBattlerTotalSpeedStat(battler2);
u32 holdEffectBattler2 = GetBattlerHoldEffect(battler2, TRUE);
u32 ability2 = GetBattlerAbility(battler2);
if (!ignoreChosenMoves)
{
if (gChosenActionByBattler[battler1] == B_ACTION_USE_MOVE)
priority1 = GetChosenMovePriority(battler1);
if (gChosenActionByBattler[battler2] == B_ACTION_USE_MOVE)
priority2 = GetChosenMovePriority(battler2);
}
return GetWhichBattlerFasterArgs(battler1, battler2, ignoreChosenMoves, ability1, ability2,
holdEffectBattler1, holdEffectBattler2, speedBattler1, speedBattler2, priority1, priority2);
}
static void SetActionsAndBattlersTurnOrder(void)
{
s32 turnOrderId = 0;
@ -4928,7 +4942,7 @@ static void SetActionsAndBattlersTurnOrder(void)
&& gActionsByTurnOrder[i] != B_ACTION_THROW_BALL
&& gActionsByTurnOrder[j] != B_ACTION_THROW_BALL)
{
if (GetWhoStrikesFirst(battler1, battler2, FALSE))
if (GetWhichBattlerFaster(battler1, battler2, FALSE))
SwapTurnOrder(i, j);
}
}
@ -5092,7 +5106,7 @@ static void TryChangeTurnOrder(void)
if (gActionsByTurnOrder[i] == B_ACTION_USE_MOVE
&& gActionsByTurnOrder[j] == B_ACTION_USE_MOVE)
{
if (GetWhoStrikesFirst(battler1, battler2, FALSE))
if (GetWhichBattlerFaster(battler1, battler2, FALSE))
SwapTurnOrder(i, j);
}
}

View File

@ -875,12 +875,12 @@ void HandleAction_ActionFinished(void)
// have been executed before. The only recalculation needed is for moves/switch. Mega evolution is handled in src/battle_main.c/TryChangeOrder
if((gActionsByTurnOrder[i] == B_ACTION_USE_MOVE && gActionsByTurnOrder[j] == B_ACTION_USE_MOVE))
{
if (GetWhoStrikesFirst(battler1, battler2, FALSE))
if (GetWhichBattlerFaster(battler1, battler2, FALSE))
SwapTurnOrder(i, j);
}
else if ((gActionsByTurnOrder[i] == B_ACTION_SWITCH && gActionsByTurnOrder[j] == B_ACTION_SWITCH))
{
if (GetWhoStrikesFirst(battler1, battler2, TRUE)) // If the actions chosen are switching, we recalc order but ignoring the moves
if (GetWhichBattlerFaster(battler1, battler2, TRUE)) // If the actions chosen are switching, we recalc order but ignoring the moves
SwapTurnOrder(i, j);
}
}
@ -2043,7 +2043,7 @@ u8 DoFieldEndTurnEffects(void)
{
if (!gProtectStructs[i].quash
&& !gProtectStructs[j].quash
&& GetWhoStrikesFirst(gBattlerByTurnOrder[i], gBattlerByTurnOrder[j], FALSE))
&& GetWhichBattlerFaster(gBattlerByTurnOrder[i], gBattlerByTurnOrder[j], FALSE))
SwapTurnOrder(i, j);
}
}
@ -8259,13 +8259,13 @@ bool32 IsBattlerAlive(u32 battler)
return TRUE;
}
u32 GetBattleMonMoveSlot(struct BattlePokemon *battleMon, u32 move)
u32 GetMoveSlot(u16 *moves, u32 move)
{
u32 i;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (battleMon->moves[i] == move)
if (moves[i] == move)
break;
}
return i;
@ -8555,8 +8555,8 @@ static inline u32 CalcMoveBasePower(u32 move, u32 battlerAtk, u32 battlerDef, u3
basePower *= 2;
break;
case EFFECT_TRUMP_CARD:
i = GetBattleMonMoveSlot(&gBattleMons[battlerAtk], move);
if (i != 4)
i = GetMoveSlot(gBattleMons[battlerAtk].moves, move);
if (i != MAX_MON_MOVES)
{
if (gBattleMons[battlerAtk].pp[i] >= ARRAY_COUNT(sTrumpCardPowerTable))
basePower = sTrumpCardPowerTable[ARRAY_COUNT(sTrumpCardPowerTable) - 1];