Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Damage roll selection in AI_CalcDamage #4615

Merged
merged 7 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions include/battle_ai_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@

#define AI_STRIKES_FIRST(battlerAi, battlerDef, move)((AI_WhoStrikesFirst(battlerAi, battlerDef, move) == AI_IS_FASTER))

enum
{
DMG_ROLL_LOWEST,
DMG_ROLL_AVERAGE,
DMG_ROLL_HIGHEST,
};

bool32 AI_RandLessThan(u32 val);
bool32 IsAiVsAiBattle(void);
bool32 BattlerHasAi(u32 battlerId);
Expand Down Expand Up @@ -82,8 +89,8 @@ bool32 ShouldLowerEvasion(u32 battlerAtk, u32 battlerDef, u32 defAbility);
bool32 IsAffectedByPowder(u32 battler, u32 ability, u32 holdEffect);
bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, u32 category);
s32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo);
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);
s32 AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 dmgRoll);
s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather, u32 dmgRoll);
bool32 AI_IsDamagedByRecoil(u32 battler);
u32 GetNoOfHitsToKO(u32 dmg, s32 hp);
u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef);
Expand Down Expand Up @@ -187,7 +194,7 @@ void IncreaseSleepScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score);
void IncreaseConfusionScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score);
void IncreaseFrostbiteScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score);

s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool8 isPartyMonAttacker);
s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool32 isPartyMonAttacker, u32 dmgRoll);
s32 AI_CheckMoveEffects(u32 battlerAtk, u32 battlerDef, u32 move, s32 score, struct AiLogicData *aiData, u32 predictedMove, bool32 isDoubleBattle);
s32 AI_TryToClearStats(u32 battlerAtk, u32 battlerDef, bool32 isDoubleBattle);
bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef);
Expand Down
3 changes: 2 additions & 1 deletion include/constants/battle_ai.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@
#define AI_FLAG_ACE_POKEMON (1 << 16) // AI has an Ace Pokemon. The last Pokemon in the party will not be used until it's the last one remaining.
#define AI_FLAG_OMNISCIENT (1 << 17) // AI has full knowledge of player moves, abilities, hold items
#define AI_FLAG_SMART_MON_CHOICES (1 << 18) // AI will make smarter decisions when choosing which mon to send out mid-battle and after a KO, which are separate decisions. Automatically included by AI_FLAG_SMART_SWITCHING.
#define AI_FLAG_CONSERVATIVE (1 << 19) // AI assumes all moves will low roll damage

#define AI_FLAG_COUNT 19
#define AI_FLAG_COUNT 20

// 'other' ai logic flags
#define AI_FLAG_ROAMING (1 << 29)
Expand Down
7 changes: 6 additions & 1 deletion src/battle_ai_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,12 @@ static void SetBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u3
//&& gMovesInfo[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);
if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_RISKY)
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE, weather, DMG_ROLL_HIGHEST);
else if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE)
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE, weather, DMG_ROLL_LOWEST);
else
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE, weather, DMG_ROLL_AVERAGE);
aiData->moveAccuracy[battlerAtk][battlerDef][i] = Ai_SetMoveAccuracy(aiData, battlerAtk, battlerDef, move);
}
aiData->simulatedDmg[battlerAtk][battlerDef][i] = dmg;
Expand Down
8 changes: 4 additions & 4 deletions src/battle_ai_switch_items.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult)
playerMove = gBattleMons[opposingBattler].moves[i];
if (playerMove != MOVE_NONE && gMovesInfo[playerMove].power != 0)
{
damageTaken = AI_CalcDamage(playerMove, opposingBattler, battler, &effectiveness, FALSE, weather);
damageTaken = AI_CalcDamage(playerMove, opposingBattler, battler, &effectiveness, FALSE, weather, DMG_ROLL_HIGHEST);
if (damageTaken > maxDamageTaken)
maxDamageTaken = damageTaken;
}
Expand Down Expand Up @@ -1234,7 +1234,7 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva
if (aiMove != MOVE_NONE && gMovesInfo[aiMove].power != 0)
{
aiMove = GetMonData(&party[i], MON_DATA_MOVE1 + j);
dmg = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE);
dmg = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE, DMG_ROLL_AVERAGE);
if (bestDmg < dmg)
{
bestDmg = dmg;
Expand Down Expand Up @@ -1687,7 +1687,7 @@ static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattle
playerMove = gBattleMons[opposingBattler].moves[i];
if (playerMove != MOVE_NONE && gMovesInfo[playerMove].power != 0)
{
damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, FALSE);
damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, FALSE, DMG_ROLL_HIGHEST);
if (damageTaken > maxDamageTaken)
maxDamageTaken = damageTaken;
}
Expand Down Expand Up @@ -1779,7 +1779,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,

// Only do damage calc if switching after KO, don't need it otherwise and saves ~0.02s per turn
if (isSwitchAfterKO && aiMove != MOVE_NONE && gMovesInfo[aiMove].power != 0)
damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE);
damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE, DMG_ROLL_AVERAGE);

// Check for Baton Pass; hitsToKO requirements mean mon can boost and BP without dying whether it's slower or not
if (aiMove == MOVE_BATON_PASS && ((hitsToKO > hitsToKOThreshold + 1 && AI_DATA->switchinCandidate.battleMon.speed < playerMonSpeed) || (hitsToKO > hitsToKOThreshold && AI_DATA->switchinCandidate.battleMon.speed > playerMonSpeed)))
Expand Down
40 changes: 31 additions & 9 deletions src/battle_ai_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -354,14 +354,14 @@ bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, u32 category)
}

// To save computation time this function has 2 variants. One saves, sets and restores battlers, while the other doesn't.
s32 AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower)
s32 AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 dmgRoll)
{
s32 dmg = 0;
SaveBattlerData(battlerAtk);
SaveBattlerData(battlerDef);
SetBattlerData(battlerAtk);
SetBattlerData(battlerDef);
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, typeEffectiveness, considerZPower, AI_GetWeather(AI_DATA));
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, typeEffectiveness, considerZPower, AI_GetWeather(AI_DATA), dmgRoll);
RestoreBattlerData(battlerAtk);
RestoreBattlerData(battlerDef);
return dmg;
Expand All @@ -374,6 +374,18 @@ static inline s32 LowestRollDmg(s32 dmg)
return dmg;
}

static inline s32 HighestRollDmg(s32 dmg)
{
return dmg;
}

static inline s32 AverageRollDmg(s32 dmg)
{
dmg = ((HighestRollDmg(dmg) + LowestRollDmg(dmg)) * 100) / 2;
dmg /= 100;
return dmg;
}

bool32 IsDamageMoveUnusable(u32 move, u32 battlerAtk, u32 battlerDef)
{
s32 moveType;
Expand Down Expand Up @@ -456,7 +468,7 @@ bool32 IsDamageMoveUnusable(u32 move, u32 battlerAtk, u32 battlerDef)
return FALSE;
}

s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather)
s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather, u32 dmgRoll)
{
s32 dmg, moveType;
uq4_12_t effectivenessMultiplier;
Expand Down Expand Up @@ -532,11 +544,21 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes
aiData->abilities[battlerAtk], aiData->abilities[battlerDef]);
u32 critChance = GetCritHitChance(critChanceIndex);
// With critChance getting closer to 1, dmg gets closer to critDmg.
dmg = LowestRollDmg((critDmg + normalDmg * (critChance - 1)) / (critChance));
if (dmgRoll == DMG_ROLL_AVERAGE)
dmg = AverageRollDmg((critDmg + normalDmg * (critChance - 1)) / (critChance));
else if (dmgRoll == DMG_ROLL_HIGHEST)
dmg = HighestRollDmg((critDmg + normalDmg * (critChance - 1)) / (critChance));
else
dmg = LowestRollDmg((critDmg + normalDmg * (critChance - 1)) / (critChance)); // Default to lowest roll
}
else
{
dmg = LowestRollDmg(normalDmg);
if (dmgRoll == DMG_ROLL_AVERAGE)
dmg = AverageRollDmg(normalDmg);
else if (dmgRoll == DMG_ROLL_HIGHEST)
dmg = HighestRollDmg(normalDmg);
else
dmg = LowestRollDmg(normalDmg); // Default to lowest roll
}

if (!gBattleStruct->zmove.active)
Expand Down Expand Up @@ -3284,7 +3306,7 @@ void FreeRestoreBattleMons(struct BattlePokemon *savedBattleMons)
}

// party logic
s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool8 isPartyMonAttacker)
s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool32 isPartyMonAttacker, u32 dmgRoll)
{
s32 dmg;
u8 effectiveness;
Expand All @@ -3304,8 +3326,8 @@ s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct Battl
SetBattlerData(battlerAtk); // set known opposing battler data
AI_THINKING_STRUCT->saved[battlerAtk].saved = FALSE;
}

dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE, AI_GetWeather(AI_DATA));
Pawkkie marked this conversation as resolved.
Show resolved Hide resolved
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE, AI_GetWeather(AI_DATA), dmgRoll);
// restores original gBattleMon struct
FreeRestoreBattleMons(savedBattleMons);
return dmg;
Expand Down Expand Up @@ -3749,7 +3771,7 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove)
else if (!IS_MOVE_STATUS(chosenMove) && IS_MOVE_STATUS(gBattleStruct->zmove.chosenZMove))
return FALSE;

if (!IS_MOVE_STATUS(chosenMove) && AI_CalcDamageSaveBattlers(chosenMove, battlerAtk, battlerDef, &effectiveness, FALSE) >= gBattleMons[battlerDef].hp)
if (!IS_MOVE_STATUS(chosenMove) && AI_CalcDamageSaveBattlers(chosenMove, battlerAtk, battlerDef, &effectiveness, FALSE, DMG_ROLL_AVERAGE) >= gBattleMons[battlerDef].hp)
return FALSE; // don't waste damaging z move if can otherwise faint target

return TRUE;
Expand Down
2 changes: 1 addition & 1 deletion test/battle/ai.c
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES);
PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); }
OPPONENT(SPECIES_PONYTA) { Level(1); Moves(MOVE_NONE); Speed(4); } // Forces switchout
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_HEADBUTT); Speed(4); } // Mid battle, AI sends out Aron
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_HEADBUTT); Speed(4); SpDefense(41); } // Mid battle, AI sends out Aron
OPPONENT(SPECIES_ELECTRODE) { Level(30); Moves(MOVE_CHARGE_BEAM); Speed(6); }
} WHEN {
TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SWITCH(opponent, 1); }
Expand Down
Loading