Improve how AI chooses damaging moves (#3199)

Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
This commit is contained in:
DizzyEggg 2023-10-02 01:36:57 +02:00 committed by GitHub
parent 8a1f166f5d
commit c69d8e0960
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 339 additions and 57 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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