Improve how AI chooses damaging moves (#3199)
Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
This commit is contained in:
parent
8a1f166f5d
commit
c69d8e0960
6 changed files with 339 additions and 57 deletions
|
@ -291,6 +291,7 @@ struct AiLogicData
|
|||
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 moveAccuracy[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
|
||||
u8 moveLimitations[MAX_BATTLERS_COUNT];
|
||||
bool8 shouldSwitchMon; // Because all available moves have no/little effect. Each bit per battler.
|
||||
u8 monToSwitchId[MAX_BATTLERS_COUNT]; // ID of the mon to switch.
|
||||
|
@ -709,11 +710,13 @@ STATIC_ASSERT(sizeof(((struct BattleStruct *)0)->palaceFlags) * 8 >= MAX_BATTLER
|
|||
#define IS_MOVE_SPECIAL(move)(GetBattleMoveSplit(move) == SPLIT_SPECIAL)
|
||||
#define IS_MOVE_STATUS(move)(gBattleMoves[move].split == SPLIT_STATUS)
|
||||
|
||||
#define IS_MOVE_RECOIL(move)(gBattleMoves[move].effect == EFFECT_RECOIL_25 \
|
||||
|| gBattleMoves[move].effect == EFFECT_RECOIL_IF_MISS \
|
||||
|| gBattleMoves[move].effect == EFFECT_RECOIL_50 \
|
||||
|| gBattleMoves[move].effect == EFFECT_RECOIL_33 \
|
||||
|| gBattleMoves[move].effect == EFFECT_RECOIL_33_STATUS)
|
||||
#define IS_EFFECT_RECOIL(effect)(effect == EFFECT_RECOIL_25 \
|
||||
|| effect == EFFECT_RECOIL_IF_MISS \
|
||||
|| effect == EFFECT_RECOIL_50 \
|
||||
|| effect == EFFECT_RECOIL_33 \
|
||||
|| effect == EFFECT_RECOIL_33_STATUS)
|
||||
|
||||
#define IS_MOVE_RECOIL(move)(IS_EFFECT_RECOIL(gBattleMoves[move].effect))
|
||||
|
||||
#define BATTLER_MAX_HP(battlerId)(gBattleMons[battlerId].hp == gBattleMons[battlerId].maxHP)
|
||||
#define TARGET_TURN_DAMAGED ((gSpecialStatuses[gBattlerTarget].physicalDmg != 0 || gSpecialStatuses[gBattlerTarget].specialDmg != 0))
|
||||
|
|
|
@ -34,8 +34,8 @@ u32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 moveConsidered);
|
|||
bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk);
|
||||
bool32 CanTargetMoveFaintAi(u32 move, u32 battlerDef, u32 battlerAtk, u32 nHits);
|
||||
bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dmgMod);
|
||||
s32 AI_GetAbility(u32 battlerId);
|
||||
u32 AI_GetHoldEffect(u32 battlerId);
|
||||
s32 AI_DecideKnownAbilityForTurn(u32 battlerId);
|
||||
u32 AI_DecideHoldEffectForTurn(u32 battlerId);
|
||||
u32 AI_GetMoveAccuracy(u32 battlerAtk, u32 battlerDef, u32 move);
|
||||
bool32 DoesBattlerIgnoreAbilityChecks(u32 atkAbility, u32 move);
|
||||
u32 AI_GetWeather(struct AiLogicData *aiData);
|
||||
|
@ -85,10 +85,15 @@ bool32 ShouldLowerEvasion(u32 battlerAtk, u32 battlerDef, u32 defAbility);
|
|||
// move checks
|
||||
bool32 IsAffectedByPowder(u32 battler, u32 ability, u32 holdEffect);
|
||||
bool32 MovesWithSplitUnusable(u32 attacker, u32 target, u32 split);
|
||||
u32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef);
|
||||
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 weather);
|
||||
bool32 AI_IsDamagedByRecoil(u32 battler);
|
||||
u32 GetNoOfHitsToKO(u32 dmg, s32 hp);
|
||||
void SetMoveDamageResult(u32 battlerAtk, u16 *moves);
|
||||
u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef);
|
||||
u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex);
|
||||
bool32 IsInIgnoredPowerfulMoveEffects(u32 effect);
|
||||
void SetMovesDamageResults(u32 battlerAtk, u16 *moves);
|
||||
u32 GetMoveDamageResult(u32 battlerAtk, u32 battlerDef, u32 moveIndex);
|
||||
u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef);
|
||||
uq4_12_t AI_GetTypeEffectiveness(u32 move, u32 battlerAtk, u32 battlerDef);
|
||||
|
|
|
@ -328,9 +328,9 @@ static void SetBattlerAiData(u32 battler, struct AiLogicData *aiData)
|
|||
{
|
||||
u32 ability, holdEffect;
|
||||
|
||||
ability = aiData->abilities[battler] = AI_GetAbility(battler);
|
||||
ability = aiData->abilities[battler] = AI_DecideKnownAbilityForTurn(battler);
|
||||
aiData->items[battler] = gBattleMons[battler].item;
|
||||
holdEffect = aiData->holdEffects[battler] = AI_GetHoldEffect(battler);
|
||||
holdEffect = aiData->holdEffects[battler] = AI_DecideHoldEffectForTurn(battler);
|
||||
aiData->holdEffectParams[battler] = GetBattlerHoldEffectParam(battler);
|
||||
aiData->predictedMoves[battler] = gLastMoves[battler];
|
||||
aiData->hpPercents[battler] = GetHealthPercentage(battler);
|
||||
|
@ -338,6 +338,19 @@ static void SetBattlerAiData(u32 battler, struct AiLogicData *aiData)
|
|||
aiData->speedStats[battler] = GetBattlerTotalSpeedStatArgs(battler, ability, holdEffect);
|
||||
}
|
||||
|
||||
static u32 Ai_SetMoveAccuracy(struct AiLogicData *aiData, u32 battlerAtk, u32 battlerDef, u32 move)
|
||||
{
|
||||
u32 accuracy;
|
||||
u32 abilityAtk = aiData->abilities[battlerAtk];
|
||||
u32 abilityDef = aiData->abilities[battlerAtk];
|
||||
if (abilityAtk == ABILITY_NO_GUARD || abilityDef == ABILITY_NO_GUARD || gBattleMoves[move].accuracy == 0) // Moves with accuracy 0 or no guard ability always hit.
|
||||
accuracy = 100;
|
||||
else
|
||||
accuracy = GetTotalAccuracy(battlerAtk, battlerDef, move, abilityAtk, abilityDef, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef]);
|
||||
|
||||
return accuracy;
|
||||
}
|
||||
|
||||
static void SetBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u32 battlersCount)
|
||||
{
|
||||
u32 battlerDef, i, weather;
|
||||
|
@ -361,16 +374,18 @@ static void SetBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u3
|
|||
|
||||
if (move != 0
|
||||
&& move != 0xFFFF
|
||||
//&& gBattleMoves[move].power != 0 /* we want to get effectiveness of status moves */
|
||||
&& !(aiData->moveLimitations[battlerAtk] & gBitTable[i])) {
|
||||
//&& gBattleMoves[move].power != 0 /* we want to get effectiveness and accuracy of status moves */
|
||||
&& !(aiData->moveLimitations[battlerAtk] & gBitTable[i]))
|
||||
{
|
||||
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE, weather);
|
||||
aiData->moveAccuracy[battlerAtk][battlerDef][i] = Ai_SetMoveAccuracy(aiData, battlerAtk, battlerDef, move);
|
||||
}
|
||||
|
||||
aiData->simulatedDmg[battlerAtk][battlerDef][i] = dmg;
|
||||
aiData->effectiveness[battlerAtk][battlerDef][i] = effectiveness;
|
||||
}
|
||||
}
|
||||
SetMoveDamageResult(battlerAtk, moves);
|
||||
SetMovesDamageResults(battlerAtk, moves);
|
||||
}
|
||||
|
||||
void SetAiLogicDataForTurn(struct AiLogicData *aiData)
|
||||
|
@ -1843,7 +1858,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
score -= 6;
|
||||
break;
|
||||
case EFFECT_RECOIL_25:
|
||||
if (aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD && aiData->abilities[battlerAtk] != ABILITY_ROCK_HEAD)
|
||||
if (AI_IsDamagedByRecoil(battlerAtk))
|
||||
{
|
||||
u32 recoilDmg = max(1, aiData->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] / 4);
|
||||
if (!ShouldUseRecoilMove(battlerAtk, battlerDef, recoilDmg, AI_THINKING_STRUCT->movesetIndex))
|
||||
|
@ -1853,7 +1868,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
break;
|
||||
case EFFECT_RECOIL_33:
|
||||
case EFFECT_RECOIL_33_STATUS:
|
||||
if (aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD && aiData->abilities[battlerAtk] != ABILITY_ROCK_HEAD)
|
||||
if (AI_IsDamagedByRecoil(battlerAtk))
|
||||
{
|
||||
u32 recoilDmg = max(1, aiData->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] / 3);
|
||||
if (!ShouldUseRecoilMove(battlerAtk, battlerDef, recoilDmg, AI_THINKING_STRUCT->movesetIndex))
|
||||
|
@ -1862,7 +1877,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
}
|
||||
break;
|
||||
case EFFECT_RECOIL_50:
|
||||
if (aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD && aiData->abilities[battlerAtk] != ABILITY_ROCK_HEAD)
|
||||
if (AI_IsDamagedByRecoil(battlerAtk))
|
||||
{
|
||||
u32 recoilDmg = max(1, aiData->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] / 2);
|
||||
if (!ShouldUseRecoilMove(battlerAtk, battlerDef, recoilDmg, AI_THINKING_STRUCT->movesetIndex))
|
||||
|
@ -2123,7 +2138,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
case EFFECT_TAUNT:
|
||||
if (gDisableStructs[battlerDef].tauntTimer > 0
|
||||
|| DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
|
||||
score--;
|
||||
score -= 10;
|
||||
break;
|
||||
case EFFECT_BESTOW:
|
||||
if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_NONE
|
||||
|
@ -2682,9 +2697,9 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
{
|
||||
// this move can faint the target
|
||||
if (aiFaster || GetMovePriority(battlerAtk, move) > 0)
|
||||
score += 4; // we go first or we're using priority move
|
||||
score += 5; // we go first or we're using priority move
|
||||
else
|
||||
score += 2;
|
||||
score += 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -3122,6 +3137,18 @@ static bool32 IsPinchBerryItemEffect(u32 holdEffect)
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
static u32 CompareMoveAccuracies(u32 battlerAtk, u32 battlerDef, u32 moveSlot1, u32 moveSlot2)
|
||||
{
|
||||
u32 acc1 = AI_DATA->moveAccuracy[battlerAtk][battlerDef][moveSlot1];
|
||||
u32 acc2 = AI_DATA->moveAccuracy[battlerAtk][battlerDef][moveSlot2];
|
||||
|
||||
if (acc1 > acc2)
|
||||
return 0;
|
||||
else if (acc2 > acc1)
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
static u32 GetAIMostDamagingMoveId(u32 battlerAtk, u32 battlerDef)
|
||||
{
|
||||
u32 i, id = 0;
|
||||
|
@ -3135,13 +3162,100 @@ static u32 GetAIMostDamagingMoveId(u32 battlerAtk, u32 battlerDef)
|
|||
return id;
|
||||
}
|
||||
|
||||
static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId)
|
||||
{
|
||||
u32 i;
|
||||
u32 noOfHits[MAX_MON_MOVES];
|
||||
s32 score = 0;
|
||||
u16 *moves = GetMovesArray(battlerAtk);
|
||||
bool8 isPowerfulIgnoredEffect[MAX_MON_MOVES];
|
||||
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (moves[i] != MOVE_NONE && gBattleMoves[moves[i]].power)
|
||||
{
|
||||
noOfHits[i] = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, i);
|
||||
isPowerfulIgnoredEffect[i] = IsInIgnoredPowerfulMoveEffects(gBattleMoves[moves[i]].effect);
|
||||
}
|
||||
else
|
||||
{
|
||||
noOfHits[i] = 0;
|
||||
isPowerfulIgnoredEffect[i] = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// if multiple moves can 0HKO, then compare the current move with the others
|
||||
if (noOfHits[currId] == 1)
|
||||
{
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (noOfHits[i] != 1 || i == currId)
|
||||
continue;
|
||||
|
||||
// prioritize moves without a risky secondary effect such as Overheat, Explosion or Hyper Beam
|
||||
if (!isPowerfulIgnoredEffect[currId] && isPowerfulIgnoredEffect[i])
|
||||
score += 2;
|
||||
else if (isPowerfulIgnoredEffect[currId] && !isPowerfulIgnoredEffect[i])
|
||||
score -= 2;
|
||||
|
||||
if (!isPowerfulIgnoredEffect[currId])
|
||||
{
|
||||
// prioritize moves with higher accuracy
|
||||
switch (CompareMoveAccuracies(battlerAtk, battlerDef, currId, i))
|
||||
{
|
||||
case 0:
|
||||
score++;
|
||||
break;
|
||||
case 1:
|
||||
score--;
|
||||
break;
|
||||
}
|
||||
|
||||
// prioritize moves without recoil
|
||||
if (AI_IsDamagedByRecoil(battlerAtk))
|
||||
{
|
||||
if (!IS_MOVE_RECOIL(moves[currId]) && IS_MOVE_RECOIL(moves[i]))
|
||||
score++;
|
||||
else if (IS_MOVE_RECOIL(moves[currId]) && !IS_MOVE_RECOIL(moves[i]))
|
||||
score--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// give priority to moves which can ko the target faster
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (i != currId && noOfHits[i] < 5 && noOfHits[currId] != 1 && noOfHits[i] < noOfHits[currId])
|
||||
{
|
||||
if (AI_WhichMoveBetter(moves[currId], moves[i], battlerAtk, battlerDef) != 0)
|
||||
score--;
|
||||
}
|
||||
}
|
||||
|
||||
// If 2 moves can KO the target in the same number of turns,
|
||||
// but one of them always hits and there is a risk the other
|
||||
// move could miss, prioritize the move with better accuracy.
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (i != currId && noOfHits[currId] == noOfHits[i]
|
||||
&& CompareMoveAccuracies(battlerAtk, battlerDef, currId, i))
|
||||
score++;
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
// AI_FLAG_CHECK_VIABILITY - a weird mix of increasing and decreasing scores
|
||||
static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
||||
{
|
||||
// move data
|
||||
u32 moveEffect = gBattleMoves[move].effect;
|
||||
struct AiLogicData *aiData = AI_DATA;
|
||||
u32 effectiveness = aiData->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex];
|
||||
u32 movesetIndex = AI_THINKING_STRUCT->movesetIndex;
|
||||
u32 effectiveness = aiData->effectiveness[battlerAtk][battlerDef][movesetIndex];
|
||||
s8 atkPriority = GetMovePriority(battlerAtk, move);
|
||||
u32 predictedMove = aiData->predictedMoves[battlerDef];
|
||||
u32 predictedMoveSlot = GetMoveSlot(GetMovesArray(battlerDef), predictedMove);
|
||||
|
@ -3154,6 +3268,9 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
|
|||
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
|
||||
return score;
|
||||
|
||||
if (gBattleMoves[move].power)
|
||||
score += AI_CompareDamagingMoves(battlerAtk, battlerDef, movesetIndex);
|
||||
|
||||
// check always hits
|
||||
if (!IS_MOVE_STATUS(move) && gBattleMoves[move].accuracy == 0)
|
||||
{
|
||||
|
@ -3162,7 +3279,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
|
|||
{
|
||||
u32 mostDmgMoveId = GetAIMostDamagingMoveId(battlerAtk, battlerDef);
|
||||
u32 *dmgs = aiData->simulatedDmg[battlerAtk][battlerDef];
|
||||
if (GetNoOfHitsToKO(dmgs[mostDmgMoveId], gBattleMons[battlerDef].hp) == GetNoOfHitsToKO(dmgs[AI_THINKING_STRUCT->movesetIndex], gBattleMons[battlerDef].hp))
|
||||
if (GetNoOfHitsToKO(dmgs[mostDmgMoveId], gBattleMons[battlerDef].hp) == GetNoOfHitsToKO(dmgs[movesetIndex], gBattleMons[battlerDef].hp))
|
||||
score++;
|
||||
}
|
||||
if (gBattleMons[battlerDef].statStages[STAT_EVASION] >= 10 || gBattleMons[battlerAtk].statStages[STAT_ACC] <= 2)
|
||||
|
@ -3187,7 +3304,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
|
|||
}
|
||||
|
||||
// check damage
|
||||
if (gBattleMoves[move].power != 0 && GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == MOVE_POWER_WEAK)
|
||||
if (gBattleMoves[move].power != 0 && GetMoveDamageResult(battlerAtk, battlerDef, movesetIndex) == MOVE_POWER_WEAK)
|
||||
score--;
|
||||
|
||||
// check status move preference
|
||||
|
@ -3247,7 +3364,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
|
|||
case ABILITY_AS_ONE_SHADOW_RIDER:
|
||||
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker should go first
|
||||
{
|
||||
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
|
||||
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, 0))
|
||||
score += 8; // prioritize killing target for stat boost
|
||||
}
|
||||
break;
|
||||
|
@ -3716,7 +3833,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
|
|||
case EFFECT_PARTING_SHOT:
|
||||
if (!IsDoubleBattle())
|
||||
{
|
||||
switch (ShouldPivot(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, AI_THINKING_STRUCT->movesetIndex))
|
||||
switch (ShouldPivot(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, movesetIndex))
|
||||
{
|
||||
case 0: // no
|
||||
score -= 10; // technically should go in CheckBadMove, but this is easier/less computationally demanding
|
||||
|
@ -3782,7 +3899,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
|
|||
{
|
||||
u32 newHp = (gBattleMons[battlerAtk].hp + gBattleMons[battlerDef].hp) / 2;
|
||||
u32 healthBenchmark = (gBattleMons[battlerAtk].hp * 12) / 10;
|
||||
if (newHp > healthBenchmark && ShouldAbsorb(battlerAtk, battlerDef, move, aiData->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]))
|
||||
if (newHp > healthBenchmark && ShouldAbsorb(battlerAtk, battlerDef, move, aiData->simulatedDmg[battlerAtk][battlerDef][movesetIndex]))
|
||||
score += 2;
|
||||
}
|
||||
break;
|
||||
|
@ -4042,6 +4159,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
|
|||
score++;
|
||||
if (HasMoveEffect(battlerDef, EFFECT_MORNING_SUN)
|
||||
|| HasMoveEffect(battlerDef, EFFECT_SYNTHESIS)
|
||||
|| HasMoveEffect(battlerDef, EFFECT_SOLAR_BEAM)
|
||||
|| HasMoveEffect(battlerDef, EFFECT_MOONLIGHT))
|
||||
score += 2;
|
||||
if (HasMoveWithType(battlerDef, TYPE_FIRE) || HasMoveWithType(BATTLE_PARTNER(battlerDef), TYPE_FIRE))
|
||||
|
@ -4071,7 +4189,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
|
|||
case EFFECT_FELL_STINGER:
|
||||
if (gBattleMons[battlerAtk].statStages[STAT_ATK] < MAX_STAT_STAGE
|
||||
&& aiData->abilities[battlerAtk] != ABILITY_CONTRARY
|
||||
&& CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
|
||||
&& CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, 0))
|
||||
{
|
||||
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker goes first
|
||||
score += 9;
|
||||
|
@ -4837,9 +4955,12 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
|
|||
IncreasePoisonScore(battlerAtk, battlerDef, move, &score);
|
||||
IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPEED, &score);
|
||||
break;
|
||||
case EFFECT_SOLAR_BEAM:
|
||||
if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, movesetIndex) >= 2
|
||||
&& HasMoveEffect(battlerAtk, EFFECT_SUNNY_DAY) && (AI_GetWeather(aiData) & B_WEATHER_SUN)) // Use Sunny Day to boost damage.
|
||||
score -= 3;
|
||||
case EFFECT_TWO_TURNS_ATTACK:
|
||||
case EFFECT_SKULL_BASH:
|
||||
case EFFECT_SOLAR_BEAM:
|
||||
if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_POWER_HERB)
|
||||
score += 2;
|
||||
break;
|
||||
|
|
|
@ -881,14 +881,140 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes
|
|||
return dmg;
|
||||
}
|
||||
|
||||
// Checks if one of the moves has side effects or perks
|
||||
static u32 WhichMoveBetter(u32 move1, u32 move2)
|
||||
bool32 AI_IsDamagedByRecoil(u32 battler)
|
||||
{
|
||||
s32 defAbility = AI_DATA->abilities[gBattlerTarget];
|
||||
u32 ability = AI_DATA->abilities[battler];
|
||||
if (ability == ABILITY_MAGIC_GUARD || ability == ABILITY_ROCK_HEAD)
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Decide whether move having an additional effect for .
|
||||
static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move)
|
||||
{
|
||||
u32 i;
|
||||
u32 abilityDef = AI_DATA->abilities[battlerDef];
|
||||
u32 abilityAtk = AI_DATA->abilities[battlerAtk];
|
||||
|
||||
switch (gBattleMoves[move].effect)
|
||||
{
|
||||
case EFFECT_HIT:
|
||||
default:
|
||||
return FALSE;
|
||||
case EFFECT_PARALYZE_HIT:
|
||||
if (AI_CanParalyze(battlerAtk, battlerDef, abilityDef, move, MOVE_NONE))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_BURN_HIT:
|
||||
if (AI_CanBurn(battlerAtk, battlerDef, abilityDef, BATTLE_PARTNER(battlerAtk), move, MOVE_NONE))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_POISON_HIT:
|
||||
case EFFECT_POISON_FANG:
|
||||
if (AI_CanPoison(battlerAtk, battlerDef, abilityDef, move, MOVE_NONE))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_FREEZE_HIT:
|
||||
if (AI_CanGetFrostbite(battlerDef, abilityDef))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_CONFUSE_HIT:
|
||||
if (AI_CanConfuse(battlerAtk, battlerDef, abilityDef, BATTLE_PARTNER(battlerAtk), move, MOVE_NONE))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_FLINCH_STATUS:
|
||||
switch (gBattleMoves[move].argument)
|
||||
{
|
||||
case STATUS1_PARALYSIS:
|
||||
if (AI_CanParalyze(battlerAtk, battlerDef, abilityDef, move, MOVE_NONE))
|
||||
return TRUE;
|
||||
break;
|
||||
case STATUS1_BURN:
|
||||
if (AI_CanBurn(battlerAtk, battlerDef, abilityDef, BATTLE_PARTNER(battlerAtk), move, MOVE_NONE))
|
||||
return TRUE;
|
||||
break;
|
||||
case STATUS1_FREEZE:
|
||||
if (AI_CanGetFrostbite(battlerDef, abilityDef))
|
||||
return TRUE;
|
||||
break;
|
||||
}
|
||||
// fallthrough
|
||||
case EFFECT_FLINCH_HIT:
|
||||
if (ShouldTryToFlinch(battlerAtk, battlerDef, abilityAtk, abilityDef, move))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_HIT_ESCAPE:
|
||||
if (CountUsablePartyMons(battlerAtk) != 0 && ShouldPivot(battlerAtk, battlerDef, abilityDef, move, AI_THINKING_STRUCT->movesetIndex))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_ATTACK_UP_HIT:
|
||||
case EFFECT_FELL_STINGER:
|
||||
if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ATK))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_DEFENSE_UP2_HIT:
|
||||
case EFFECT_DEFENSE_UP_HIT:
|
||||
if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_DEF))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_SPEED_UP_HIT:
|
||||
if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPEED))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_SPECIAL_ATTACK_UP_HIT:
|
||||
if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPATK))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_ATTACK_DOWN_HIT:
|
||||
if (ShouldLowerStat(battlerDef, abilityDef, STAT_ATK) && abilityDef != ABILITY_HYPER_CUTTER)
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_DEFENSE_DOWN_HIT:
|
||||
if (ShouldLowerStat(battlerDef, abilityDef, STAT_DEF))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_SPEED_DOWN_HIT:
|
||||
if (ShouldLowerStat(battlerDef, abilityDef, STAT_SPEED))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_SPECIAL_ATTACK_DOWN_HIT:
|
||||
if (ShouldLowerStat(battlerDef, abilityDef, STAT_SPATK))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_SPECIAL_DEFENSE_DOWN_HIT:
|
||||
case EFFECT_SPECIAL_DEFENSE_DOWN_HIT_2:
|
||||
if (ShouldLowerStat(battlerDef, abilityDef, STAT_SPDEF))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_ACCURACY_DOWN_HIT:
|
||||
if (ShouldLowerStat(battlerDef, abilityDef, STAT_ACC))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_EVASION_DOWN_HIT:
|
||||
if (ShouldLowerStat(battlerDef, abilityDef, STAT_EVASION))
|
||||
return TRUE;
|
||||
break;
|
||||
case EFFECT_ALL_STATS_UP_HIT:
|
||||
for (i = STAT_ATK; i <= NUM_STATS; i++)
|
||||
{
|
||||
if (BattlerStatCanRise(battlerAtk, abilityAtk, i))
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Checks if one of the moves has side effects or perks, assuming equal dmg or equal no of hits to KO
|
||||
u32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef)
|
||||
{
|
||||
bool32 effect1, effect2;
|
||||
s32 defAbility = AI_DATA->abilities[battlerDef];
|
||||
|
||||
// Check if physical moves hurt.
|
||||
if (AI_DATA->holdEffects[sBattler_AI] != HOLD_EFFECT_PROTECTIVE_PADS
|
||||
&& (BATTLE_HISTORY->itemEffects[gBattlerTarget] == HOLD_EFFECT_ROCKY_HELMET
|
||||
if (AI_DATA->holdEffects[battlerAtk] != HOLD_EFFECT_PROTECTIVE_PADS
|
||||
&& (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_ROCKY_HELMET
|
||||
|| defAbility == ABILITY_IRON_BARBS || defAbility == ABILITY_ROUGH_SKIN))
|
||||
{
|
||||
if (IS_MOVE_PHYSICAL(move1) && !IS_MOVE_PHYSICAL(move2))
|
||||
|
@ -897,11 +1023,10 @@ static u32 WhichMoveBetter(u32 move1, u32 move2)
|
|||
return 0;
|
||||
}
|
||||
// Check recoil
|
||||
if (GetBattlerAbility(sBattler_AI) != ABILITY_ROCK_HEAD)
|
||||
if (AI_IsDamagedByRecoil(battlerAtk))
|
||||
{
|
||||
if (IS_MOVE_RECOIL(move1) && !IS_MOVE_RECOIL(move2) && gBattleMoves[move2].effect != EFFECT_RECHARGE)
|
||||
return 1;
|
||||
|
||||
if (IS_MOVE_RECOIL(move2) && !IS_MOVE_RECOIL(move1) && gBattleMoves[move1].effect != EFFECT_RECHARGE)
|
||||
return 0;
|
||||
}
|
||||
|
@ -911,9 +1036,11 @@ static u32 WhichMoveBetter(u32 move1, u32 move2)
|
|||
if (gBattleMoves[move2].effect == EFFECT_RECHARGE && gBattleMoves[move1].effect != EFFECT_RECHARGE)
|
||||
return 0;
|
||||
// Check additional effect.
|
||||
if (gBattleMoves[move1].effect == 0 && gBattleMoves[move2].effect != 0)
|
||||
effect1 = AI_IsMoveEffectInPlus(battlerAtk, battlerDef, move1);
|
||||
effect2 = AI_IsMoveEffectInPlus(battlerAtk, battlerDef, move2);
|
||||
if (effect2 && !effect1)
|
||||
return 1;
|
||||
if (gBattleMoves[move2].effect == 0 && gBattleMoves[move1].effect != 0)
|
||||
if (effect1 && !effect2)
|
||||
return 0;
|
||||
|
||||
return 2;
|
||||
|
@ -921,10 +1048,38 @@ static u32 WhichMoveBetter(u32 move1, u32 move2)
|
|||
|
||||
u32 GetNoOfHitsToKO(u32 dmg, s32 hp)
|
||||
{
|
||||
if (dmg == 0)
|
||||
return 0;
|
||||
return hp / (dmg + 1) + 1;
|
||||
}
|
||||
|
||||
void SetMoveDamageResult(u32 battlerAtk, u16 *moves)
|
||||
u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef)
|
||||
{
|
||||
return GetNoOfHitsToKO(dmg, gBattleMons[battlerDef].hp);
|
||||
}
|
||||
|
||||
u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex)
|
||||
{
|
||||
return GetNoOfHitsToKOBattlerDmg(AI_DATA->simulatedDmg[battlerAtk][battlerDef][moveIndex], battlerDef);
|
||||
}
|
||||
|
||||
bool32 IsInIgnoredPowerfulMoveEffects(u32 effect)
|
||||
{
|
||||
u32 i;
|
||||
for (i = 0; sIgnoredPowerfulMoveEffects[i] != IGNORED_MOVES_END; i++)
|
||||
{
|
||||
if (effect == sIgnoredPowerfulMoveEffects[i])
|
||||
{
|
||||
// Don't ingore Solar Beam if doesn't have a charging turn.
|
||||
if (effect == EFFECT_SOLAR_BEAM && (AI_GetWeather(AI_DATA) & B_WEATHER_SUN))
|
||||
break;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void SetMovesDamageResults(u32 battlerAtk, u16 *moves)
|
||||
{
|
||||
s32 i, j, battlerDef, bestId, currId, hp, result;
|
||||
s32 moveDmgs[MAX_MON_MOVES];
|
||||
|
@ -933,16 +1088,14 @@ void SetMoveDamageResult(u32 battlerAtk, u16 *moves)
|
|||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
u32 move = moves[i];
|
||||
for (j = 0; sIgnoredPowerfulMoveEffects[j] != IGNORED_MOVES_END; j++)
|
||||
{
|
||||
if (gBattleMoves[move].effect == sIgnoredPowerfulMoveEffects[j])
|
||||
break;
|
||||
}
|
||||
if (move == 0 || move == 0xFFFF || gBattleMoves[move].power == 0 || sIgnoredPowerfulMoveEffects[j] != IGNORED_MOVES_END)
|
||||
if (move == MOVE_NONE || move == MOVE_UNAVAILABLE || gBattleMoves[move].power == 0 || IsInIgnoredPowerfulMoveEffects(gBattleMoves[move].effect))
|
||||
isNotConsidered[i] = TRUE;
|
||||
else
|
||||
isNotConsidered[i] = FALSE;
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
for (battlerDef = 0; battlerDef < MAX_BATTLERS_COUNT; battlerDef++)
|
||||
{
|
||||
if (battlerDef == battlerAtk)
|
||||
|
@ -950,7 +1103,7 @@ void SetMoveDamageResult(u32 battlerAtk, u16 *moves)
|
|||
|
||||
if (isNotConsidered[i])
|
||||
{
|
||||
AI_DATA->moveDmgResult[battlerAtk][battlerDef][i] = MOVE_POWER_OTHER; // Move has a power of 0/1, or is in the group sIgnoredPowerfulMoveEffects
|
||||
result = MOVE_POWER_OTHER; // Move has a power of 0/1, or is in the group sIgnoredPowerfulMoveEffects
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -972,13 +1125,14 @@ void SetMoveDamageResult(u32 battlerAtk, u16 *moves)
|
|||
moveDmgs[j] = hp;
|
||||
}
|
||||
|
||||
// Find move which deals most damage, in case of a tie prioritize one with better effect.
|
||||
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]))
|
||||
switch (AI_WhichMoveBetter(gBattleMons[battlerAtk].moves[bestId], gBattleMons[battlerAtk].moves[j], battlerAtk, battlerDef))
|
||||
{
|
||||
case 2:
|
||||
if (Random() & 1)
|
||||
|
@ -995,13 +1149,12 @@ void SetMoveDamageResult(u32 battlerAtk, u16 *moves)
|
|||
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)
|
||||
&& AI_WhichMoveBetter(gBattleMons[battlerAtk].moves[bestId], gBattleMons[battlerAtk].moves[currId], battlerAtk, battlerDef) != 0)
|
||||
result = MOVE_POWER_GOOD;
|
||||
else
|
||||
result = MOVE_POWER_WEAK;
|
||||
|
||||
AI_DATA->moveDmgResult[battlerAtk][battlerDef][i] = result;
|
||||
}
|
||||
AI_DATA->moveDmgResult[battlerAtk][battlerDef][i] = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1216,7 +1369,7 @@ bool32 AI_IsAbilityOnSide(u32 battlerId, u32 ability)
|
|||
}
|
||||
|
||||
// does NOT include ability suppression checks
|
||||
s32 AI_GetAbility(u32 battlerId)
|
||||
s32 AI_DecideKnownAbilityForTurn(u32 battlerId)
|
||||
{
|
||||
u32 knownAbility = GetBattlerAbility(battlerId);
|
||||
|
||||
|
@ -1254,7 +1407,7 @@ s32 AI_GetAbility(u32 battlerId)
|
|||
return ABILITY_NONE; // Unknown.
|
||||
}
|
||||
|
||||
u32 AI_GetHoldEffect(u32 battlerId)
|
||||
u32 AI_DecideHoldEffectForTurn(u32 battlerId)
|
||||
{
|
||||
u32 holdEffect;
|
||||
|
||||
|
|
|
@ -857,9 +857,9 @@ static void PutAiInfoText(struct BattleDebugMenu *data)
|
|||
{
|
||||
if (GetBattlerSide(i) == B_SIDE_PLAYER && IsBattlerAlive(i))
|
||||
{
|
||||
u16 ability = AI_GetAbility(i);
|
||||
u16 holdEffect = AI_GetHoldEffect(i);
|
||||
u16 item = gBattleMons[i].item;
|
||||
u16 ability = AI_DATA->abilities[i];
|
||||
u16 holdEffect = AI_DATA->holdEffects[i];
|
||||
u16 item = AI_DATA->items[i];
|
||||
u8 x = (i == B_POSITION_PLAYER_LEFT) ? 83 + (i) * 75 : 83 + (i-1) * 75;
|
||||
AddTextPrinterParameterized(data->aiMovesWindowId, 0, gAbilityNames[ability], x, 0, 0, NULL);
|
||||
AddTextPrinterParameterized(data->aiMovesWindowId, 0, ItemId_GetName(item), x, 15, 0, NULL);
|
||||
|
|
|
@ -1625,10 +1625,10 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u
|
|||
{
|
||||
u32 calc, moveAcc;
|
||||
s8 buff, accStage, evasionStage;
|
||||
u8 atkParam = GetBattlerHoldEffectParam(battlerAtk);
|
||||
u8 defParam = GetBattlerHoldEffectParam(battlerDef);
|
||||
u8 atkAlly = BATTLE_PARTNER(battlerAtk);
|
||||
u16 atkAllyAbility = GetBattlerAbility(atkAlly);
|
||||
u32 atkParam = GetBattlerHoldEffectParam(battlerAtk);
|
||||
u32 defParam = GetBattlerHoldEffectParam(battlerDef);
|
||||
u32 atkAlly = BATTLE_PARTNER(battlerAtk);
|
||||
u32 atkAllyAbility = GetBattlerAbility(atkAlly);
|
||||
|
||||
gPotentialItemEffectBattler = battlerDef;
|
||||
accStage = gBattleMons[battlerAtk].statStages[STAT_ACC];
|
||||
|
|
Loading…
Reference in a new issue