Merge pull request #2036 from ghoulslash/be/ai

AI Optimizations
This commit is contained in:
BuffelSaft 2022-07-04 23:16:08 +12:00 committed by GitHub
commit ead52e0b48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 912 additions and 889 deletions

View file

@ -247,33 +247,20 @@ struct AI_SavedBattleMon
struct AiLogicData
{
//attacker data
u16 atkAbility;
u16 atkItem;
u16 atkHoldEffect;
u8 atkParam;
u16 atkSpecies;
// target data
u16 defAbility;
u16 defItem;
u16 defHoldEffect;
u8 defParam;
u16 defSpecies;
// attacker partner data
u8 battlerAtkPartner;
u16 abilities[MAX_BATTLERS_COUNT];
u16 items[MAX_BATTLERS_COUNT];
u16 holdEffects[MAX_BATTLERS_COUNT];
u8 holdEffectParams[MAX_BATTLERS_COUNT];
u16 predictedMoves[MAX_BATTLERS_COUNT];
u8 hpPercents[MAX_BATTLERS_COUNT];
u16 partnerMove;
u16 atkPartnerAbility;
u16 atkPartnerHoldEffect;
bool32 targetSameSide;
// target partner data
u8 battlerDefPartner;
u16 defPartnerAbility;
u16 defPartnerHoldEffect;
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];
};
struct AI_ThinkingStruct
{
struct AiLogicData data;
u8 aiState;
u8 movesetIndex;
u16 moveConsidered;
@ -282,7 +269,6 @@ struct AI_ThinkingStruct
u32 aiFlags;
u8 aiAction;
u8 aiLogicId;
s32 simulatedDmg[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, move
struct AI_SavedBattleMon saved[4];
bool8 switchMon; // Because all available moves have no/little effect.
};
@ -326,13 +312,14 @@ struct BattleResources
struct BattleCallbacksStack* battleCallbackStack;
struct StatsArray* beforeLvlUp;
struct AI_ThinkingStruct *ai;
struct AiLogicData *aiData;
struct BattleHistory *battleHistory;
u8 bufferA[MAX_BATTLERS_COUNT][0x200];
u8 bufferB[MAX_BATTLERS_COUNT][0x200];
};
#define AI_THINKING_STRUCT ((struct AI_ThinkingStruct *)(gBattleResources->ai))
#define AI_DATA ((struct AiLogicData *)(&gBattleResources->ai->data))
#define AI_DATA ((struct AiLogicData *)(gBattleResources->aiData))
#define BATTLE_HISTORY ((struct BattleHistory *)(gBattleResources->battleHistory))
struct BattleResults
@ -597,7 +584,8 @@ struct BattleStruct
bool8 spriteIgnore0Hp;
struct Illusion illusion[MAX_BATTLERS_COUNT];
s8 aiFinalScore[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // AI, target, moves to make debugging easier
s32 aiSimulatedDamage[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, move to make debugging easier
u8 aiMoveOrAction[MAX_BATTLERS_COUNT];
u8 aiChosenTarget[MAX_BATTLERS_COUNT];
u8 soulheartBattlerId;
u8 friskedBattler; // Frisk needs to identify 2 battlers in double battles.
bool8 friskedAbility; // If identifies two mons, show the ability pop-up only once.

View file

@ -19,11 +19,12 @@
return score; \
}
u8 ComputeBattleAiScores(u8 battler);
void BattleAI_SetupItems(void);
void BattleAI_SetupFlags(void);
void BattleAI_SetupAIData(u8 defaultScoreMoves);
u8 BattleAI_ChooseMoveOrAction(void);
void GetAiLogicData(void);
extern u8 sBattler_AI;

View file

@ -20,6 +20,7 @@ void ClearBattlerItemEffectHistory(u8 battlerId);
void SaveBattlerData(u8 battlerId);
void SetBattlerData(u8 battlerId);
void RestoreBattlerData(u8 battlerId);
u16 GetAIChosenMove(u8 battlerId);
bool32 WillAIStrikeFirst(void);
u32 GetTotalBaseStat(u32 species);
@ -27,13 +28,13 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler);
bool32 AtMaxHp(u8 battler);
u32 GetHealthPercentage(u8 battler);
bool32 IsBattlerTrapped(u8 battler, bool8 switching);
u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2);
u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2, u16 consideredMove);
bool32 CanTargetFaintAi(u8 battlerDef, u8 battlerAtk);
bool32 CanMoveFaintBattler(u16 move, u8 battlerDef, u8 battlerAtk, u8 nHits);
bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgMod);
s32 AI_GetAbility(u32 battlerId);
u16 AI_GetHoldEffect(u32 battlerId);
u32 AI_GetMoveAccuracy(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbility, u8 atkHoldEffect, u8 defHoldEffect, u16 move);
u32 AI_GetMoveAccuracy(u8 battlerAtk, u8 battlerDef, u16 move);
bool32 DoesBattlerIgnoreAbilityChecks(u16 atkAbility, u16 move);
bool32 AI_WeatherHasEffect(void);
bool32 CanAIFaintTarget(u8 battlerAtk, u8 battlerDef, u8 numHits);
@ -45,7 +46,7 @@ bool32 HasDamagingMoveOfType(u8 battlerId, u8 type);
u32 GetBattlerSecondaryDamage(u8 battlerId);
bool32 BattlerWillFaintFromWeather(u8 battler, u16 ability);
bool32 BattlerWillFaintFromSecondaryDamage(u8 battler, u16 ability);
bool32 ShouldTryOHKO(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbility, u32 accuracy, u16 move);
bool32 ShouldTryOHKO(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbility, u16 move);
bool32 ShouldUseRecoilMove(u8 battlerAtk, u8 battlerDef, u32 recoilDmg, u8 moveIndex);
u16 GetBattlerSideSpeedAverage(u8 battler);
bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage);
@ -81,7 +82,7 @@ bool32 ShouldLowerEvasion(u8 battlerAtk, u8 battlerDef, u16 defAbility);
// move checks
bool32 IsAffectedByPowder(u8 battler, u16 ability, u16 holdEffect);
bool32 MovesWithSplitUnusable(u32 attacker, u32 target, u32 split);
s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef);
s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 *effectiveness);
u8 GetMoveDamageResult(u16 move);
u32 GetCurrDamageHpPercent(u8 battlerAtk, u8 battlerDef);
u16 AI_GetTypeEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef);
@ -144,7 +145,7 @@ bool32 ShouldTrap(u8 battlerAtk, u8 battlerDef, u16 move);
bool32 IsWakeupTurn(u8 battler);
// partner logic
u16 GetAllyChosenMove(void);
u16 GetAllyChosenMove(u8 battlerId);
bool32 IsValidDoubleBattle(u8 battlerAtk);
bool32 IsTargetingPartner(u8 battlerAtk, u8 battlerDef);
bool32 DoesPartnerHaveSameMoveEffect(u8 battlerAtkPartner, u8 battlerDef, u16 move, u16 partnerMove);

View file

@ -15,7 +15,7 @@ struct StatFractions
s32 CalcCritChanceStage(u8 battlerAtk, u8 battlerDef, u32 move, bool32 recordAbility);
s8 GetInverseCritChance(u8 battlerAtk, u8 battlerDef, u32 move);
u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move);
u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect);
u8 GetBattlerTurnOrderNum(u8 battlerId);
bool32 NoAliveMonsForEitherParty(void);
void SetMoveEffect(bool32 primary, u32 certain);

View file

@ -129,6 +129,7 @@ bool32 IsBattlerAlive(u8 battlerId);
u8 GetBattleMonMoveSlot(struct BattlePokemon *battleMon, u16 move);
u32 GetBattlerWeight(u8 battlerId);
s32 CalculateMoveDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, s32 fixedBasePower, bool32 isCrit, bool32 randomFactor, bool32 updateFlags);
s32 CalculateMoveDamageAndEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, u16 *typeEffectivenessModifier);
u16 CalcTypeEffectivenessMultiplier(u16 move, u8 moveType, u8 battlerAtk, u8 battlerDef, bool32 recordAbilities);
u16 CalcPartyMonTypeEffectivenessMultiplier(u16 move, u16 speciesDef, u16 abilityDef);
u16 GetTypeModifier(u8 atkType, u8 defType);

View file

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

View file

@ -15,13 +15,13 @@
#define AI_TYPE_MOVE 4
// type effectiveness
#define AI_EFFECTIVENESS_x8 320
#define AI_EFFECTIVENESS_x4 160
#define AI_EFFECTIVENESS_x2 80
#define AI_EFFECTIVENESS_x1 40
#define AI_EFFECTIVENESS_x0_5 20
#define AI_EFFECTIVENESS_x0_25 10
#define AI_EFFECTIVENESS_x0_125 5
#define AI_EFFECTIVENESS_x8 7
#define AI_EFFECTIVENESS_x4 6
#define AI_EFFECTIVENESS_x2 5
#define AI_EFFECTIVENESS_x1 4
#define AI_EFFECTIVENESS_x0_5 3
#define AI_EFFECTIVENESS_x0_25 2
#define AI_EFFECTIVENESS_x0_125 1
#define AI_EFFECTIVENESS_x0 0
// ai weather

File diff suppressed because it is too large Load diff

View file

@ -18,6 +18,8 @@
static bool8 HasSuperEffectiveMoveAgainstOpponents(bool8 noRng);
static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent);
static bool8 ShouldUseItem(void);
static bool32 AI_ShouldHeal(u32 healAmount);
static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount);
void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId)
{
@ -787,6 +789,25 @@ static u8 GetAI_ItemType(u16 itemId, const u8 *itemEffect)
return AI_ITEM_NOT_RECOGNIZABLE;
}
static bool32 AiExpectsToFaintPlayer(void)
{
bool32 canFaintPlayer;
u32 i;
u8 target = gBattleStruct->aiChosenTarget[gActiveBattler];
if (gBattleStruct->aiMoveOrAction[gActiveBattler] > 3)
return FALSE; // AI not planning to use move
if (GetBattlerSide(target) != GetBattlerSide(gActiveBattler)
&& CanIndexMoveFaintTarget(gActiveBattler, target, gBattleStruct->aiMoveOrAction[gActiveBattler], 0)
&& AI_WhoStrikesFirst(gActiveBattler, target, GetAIChosenMove(gActiveBattler)) == AI_IS_FASTER) {
// We expect to faint the target and move first -> dont use an item
return TRUE;
}
return FALSE;
}
static bool8 ShouldUseItem(void)
{
struct Pokemon *party;
@ -801,6 +822,9 @@ static bool8 ShouldUseItem(void)
if (gStatuses3[gActiveBattler] & STATUS3_EMBARGO)
return FALSE;
if (AiExpectsToFaintPlayer())
return FALSE;
if (GetBattlerSide(gActiveBattler) == B_SIDE_PLAYER)
party = gPlayerParty;
@ -842,21 +866,10 @@ static bool8 ShouldUseItem(void)
switch (*(gBattleStruct->AI_itemType + gActiveBattler / 2))
{
case AI_ITEM_FULL_RESTORE:
if (gBattleMons[gActiveBattler].hp >= gBattleMons[gActiveBattler].maxHP / 4)
break;
if (gBattleMons[gActiveBattler].hp == 0)
break;
shouldUse = TRUE;
shouldUse = AI_ShouldHeal(0);
break;
case AI_ITEM_HEAL_HP:
paramOffset = GetItemEffectParamOffset(item, 4, 4);
if (paramOffset == 0)
break;
if (gBattleMons[gActiveBattler].hp == 0)
break;
if (gBattleMons[gActiveBattler].hp < gBattleMons[gActiveBattler].maxHP / 4
|| gBattleMons[gActiveBattler].maxHP - gBattleMons[gActiveBattler].hp > itemEffects[paramOffset])
shouldUse = TRUE;
shouldUse = AI_ShouldHeal(itemEffects[GetItemEffectParamOffset(item, 4, 4)]);
break;
case AI_ITEM_CURE_CONDITION:
*(gBattleStruct->AI_itemFlags + gActiveBattler / 2) = 0;
@ -947,3 +960,32 @@ static bool8 ShouldUseItem(void)
return FALSE;
}
static bool32 AI_ShouldHeal(u32 healAmount)
{
bool32 shouldHeal = FALSE;
if (gBattleMons[gActiveBattler].hp < gBattleMons[gActiveBattler].maxHP / 4
|| gBattleMons[gActiveBattler].hp == 0
|| (healAmount != 0 && gBattleMons[gActiveBattler].maxHP - gBattleMons[gActiveBattler].hp > healAmount)) {
// We have low enough HP to consider healing
shouldHeal = !AI_OpponentCanFaintAiWithMod(healAmount); // if target can kill us even after we heal, why bother
}
return shouldHeal;
}
static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount)
{
u32 i;
// Check special cases to NOT heal
for (i = 0; i < gBattlersCount; i++) {
if (GetBattlerSide(i) == B_SIDE_PLAYER) {
if (CanTargetFaintAiWithMod(i, gActiveBattler, healAmount, 0)) {
// Target is expected to faint us
return TRUE;
}
}
}
return FALSE;
}

View file

@ -20,6 +20,8 @@
#include "constants/moves.h"
#include "constants/items.h"
static u32 AI_GetEffectiveness(u16 multiplier);
// Const Data
static const s8 sAiAbilityRatings[ABILITIES_COUNT] =
{
@ -438,9 +440,14 @@ static const u16 sOtherMoveCallingMoves[] =
};
// Functions
u16 GetAIChosenMove(u8 battlerId)
{
return (gBattleMons[battlerId].moves[gBattleStruct->aiMoveOrAction[battlerId]]);
}
bool32 WillAIStrikeFirst(void)
{
return (AI_WhoStrikesFirst(sBattler_AI, gBattlerTarget) == AI_IS_FASTER);
return (AI_WhoStrikesFirst(sBattler_AI, gBattlerTarget, AI_THINKING_STRUCT->moveConsidered) == AI_IS_FASTER);
}
bool32 AI_RandLessThan(u8 val)
@ -590,14 +597,14 @@ u32 GetHealthPercentage(u8 battlerId)
bool32 AtMaxHp(u8 battlerId)
{
if (GetHealthPercentage(battlerId) == 100)
if (AI_DATA->hpPercents[battlerId] == 100)
return TRUE;
return FALSE;
}
bool32 IsBattlerTrapped(u8 battler, bool8 checkSwitch)
{
u8 holdEffect = AI_GetHoldEffect(battler);
u8 holdEffect = AI_DATA->holdEffects[battler];
if (IS_BATTLER_OF_TYPE(battler, TYPE_GHOST)
|| (checkSwitch && holdEffect == HOLD_EFFECT_SHED_SHELL)
|| (!checkSwitch && GetBattlerAbility(battler) == ABILITY_RUN_AWAY)
@ -636,7 +643,7 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler)
u32 move = gBattleResources->battleHistory->usedMoves[opposingBattler][i];
if (gBattleMoves[move].effect == EFFECT_PROTECT && move != MOVE_ENDURE)
return TRUE;
if (gBattleMoves[move].effect == EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAI, opposingBattler) == AI_IS_SLOWER)
if (gBattleMoves[move].effect == EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAI, opposingBattler, GetAIChosenMove(battlerAI)) == AI_IS_SLOWER)
return TRUE;
}
return FALSE;
@ -658,7 +665,7 @@ bool32 MovesWithSplitUnusable(u32 attacker, u32 target, u32 split)
{
s32 i, moveType;
u32 usable = 0;
u32 unusable = CheckMoveLimitations(attacker, 0, MOVE_LIMITATIONS_ALL);
u32 unusable = AI_DATA->moveLimitations[attacker];
u16 *moves = GetMovesArray(attacker);
for (i = 0; i < MAX_MON_MOVES; i++)
@ -713,10 +720,11 @@ static bool32 AI_GetIfCrit(u32 move, u8 battlerAtk, u8 battlerDef)
return isCrit;
}
s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef)
s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 *typeEffectiveness)
{
s32 dmg, moveType, critDmg, normalDmg;
s8 critChance;
u16 effectivenessMultiplier;
SaveBattlerData(battlerAtk);
SaveBattlerData(battlerDef);
@ -728,56 +736,69 @@ s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef)
SetTypeBeforeUsingMove(move, battlerAtk);
GET_MOVE_TYPE(move, moveType);
critChance = GetInverseCritChance(battlerAtk, battlerDef, move);
normalDmg = CalculateMoveDamage(move, battlerAtk, battlerDef, moveType, 0, FALSE, FALSE, FALSE);
critDmg = CalculateMoveDamage(move, battlerAtk, battlerDef, moveType, 0, TRUE, FALSE, FALSE);
if(critChance == -1)
dmg = normalDmg;
else
dmg = (critDmg + normalDmg * (critChance - 1)) / critChance;
// Handle dynamic move damage
switch (gBattleMoves[move].effect)
if (gBattleMoves[move].power)
{
case EFFECT_LEVEL_DAMAGE:
case EFFECT_PSYWAVE:
dmg = gBattleMons[battlerAtk].level * (AI_DATA->atkAbility == ABILITY_PARENTAL_BOND ? 2 : 1);
break;
case EFFECT_DRAGON_RAGE:
dmg = 40 * (AI_DATA->atkAbility == ABILITY_PARENTAL_BOND ? 2 : 1);
break;
case EFFECT_SONICBOOM:
dmg = 20 * (AI_DATA->atkAbility == ABILITY_PARENTAL_BOND ? 2 : 1);
break;
case EFFECT_MULTI_HIT:
dmg *= (AI_DATA->atkAbility == ABILITY_SKILL_LINK ? 5 : 3);
break;
case EFFECT_TRIPLE_KICK:
dmg *= (AI_DATA->atkAbility == ABILITY_SKILL_LINK ? 6 : 5);
break;
case EFFECT_ENDEAVOR:
// If target has less HP than user, Endeavor does no damage
dmg = max(0, gBattleMons[battlerDef].hp - gBattleMons[battlerAtk].hp);
break;
case EFFECT_SUPER_FANG:
dmg = (AI_DATA->atkAbility == ABILITY_PARENTAL_BOND
? max(2, gBattleMons[battlerDef].hp * 3 / 4)
: max(1, gBattleMons[battlerDef].hp / 2));
break;
case EFFECT_FINAL_GAMBIT:
dmg = gBattleMons[battlerAtk].hp;
break;
}
critChance = GetInverseCritChance(battlerAtk, battlerDef, move);
normalDmg = CalculateMoveDamageAndEffectiveness(move, battlerAtk, battlerDef, moveType, &effectivenessMultiplier);
critDmg = CalculateMoveDamage(move, battlerAtk, battlerDef, moveType, 0, TRUE, FALSE, FALSE);
// Handle other multi-strike moves
if (gBattleMoves[move].flags & FLAG_TWO_STRIKES)
dmg *= 2;
else if (move == MOVE_SURGING_STRIKES || (move == MOVE_WATER_SHURIKEN && gBattleMons[battlerAtk].species == SPECIES_GRENINJA_ASH))
dmg *= 3;
if (critChance == -1)
dmg = normalDmg;
else
dmg = (critDmg + normalDmg * (critChance - 1)) / critChance;
// Handle dynamic move damage
switch (gBattleMoves[move].effect)
{
case EFFECT_LEVEL_DAMAGE:
case EFFECT_PSYWAVE:
dmg = gBattleMons[battlerAtk].level * (AI_DATA->abilities[battlerAtk] == ABILITY_PARENTAL_BOND ? 2 : 1);
break;
case EFFECT_DRAGON_RAGE:
dmg = 40 * (AI_DATA->abilities[battlerAtk] == ABILITY_PARENTAL_BOND ? 2 : 1);
break;
case EFFECT_SONICBOOM:
dmg = 20 * (AI_DATA->abilities[battlerAtk] == ABILITY_PARENTAL_BOND ? 2 : 1);
break;
case EFFECT_MULTI_HIT:
dmg *= (AI_DATA->abilities[battlerAtk] == ABILITY_SKILL_LINK ? 5 : 3);
break;
case EFFECT_TRIPLE_KICK:
dmg *= (AI_DATA->abilities[battlerAtk] == ABILITY_SKILL_LINK ? 6 : 5);
break;
case EFFECT_ENDEAVOR:
// If target has less HP than user, Endeavor does no damage
dmg = max(0, gBattleMons[battlerDef].hp - gBattleMons[battlerAtk].hp);
break;
case EFFECT_SUPER_FANG:
dmg = (AI_DATA->abilities[battlerAtk] == ABILITY_PARENTAL_BOND
? max(2, gBattleMons[battlerDef].hp * 3 / 4)
: max(1, gBattleMons[battlerDef].hp / 2));
break;
case EFFECT_FINAL_GAMBIT:
dmg = gBattleMons[battlerAtk].hp;
break;
}
// Handle other multi-strike moves
if (gBattleMoves[move].flags & FLAG_TWO_STRIKES)
dmg *= 2;
else if (move == MOVE_SURGING_STRIKES || (move == MOVE_WATER_SHURIKEN && gBattleMons[battlerAtk].species == SPECIES_GRENINJA_ASH))
dmg *= 3;
if (dmg == 0)
dmg = 1;
}
else
{
dmg = 0;
}
RestoreBattlerData(battlerAtk);
RestoreBattlerData(battlerDef);
// convert multiper to AI_EFFECTIVENESS_xX
*typeEffectiveness = AI_GetEffectiveness(effectivenessMultiplier);
return dmg;
}
@ -785,10 +806,10 @@ s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef)
// Checks if one of the moves has side effects or perks
static u32 WhichMoveBetter(u32 move1, u32 move2)
{
s32 defAbility = AI_GetAbility(gBattlerTarget);
s32 defAbility = AI_DATA->abilities[gBattlerTarget];
// Check if physical moves hurt.
if (AI_GetHoldEffect(sBattler_AI) != HOLD_EFFECT_PROTECTIVE_PADS
if (AI_DATA->holdEffects[sBattler_AI] != HOLD_EFFECT_PROTECTIVE_PADS
&& (BATTLE_HISTORY->itemEffects[gBattlerTarget] == HOLD_EFFECT_ROCKY_HELMET
|| defAbility == ABILITY_IRON_BARBS || defAbility == ABILITY_ROUGH_SKIN))
{
@ -868,7 +889,7 @@ u8 GetMoveDamageResult(u16 move)
&& sIgnoredPowerfulMoveEffects[i] == IGNORED_MOVES_END
&& gBattleMoves[gBattleMons[sBattler_AI].moves[checkedMove]].power != 0)
{
moveDmgs[checkedMove] = AI_THINKING_STRUCT->simulatedDmg[sBattler_AI][gBattlerTarget][checkedMove];
moveDmgs[checkedMove] = AI_DATA->simulatedDmg[sBattler_AI][gBattlerTarget][checkedMove];
}
else
{
@ -924,7 +945,7 @@ u8 GetMoveDamageResult(u16 move)
u32 GetCurrDamageHpPercent(u8 battlerAtk, u8 battlerDef)
{
int bestDmg = AI_THINKING_STRUCT->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex];
int bestDmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex];
return (bestDmg * 100) / gBattleMons[battlerDef].maxHP;
}
@ -952,42 +973,32 @@ u16 AI_GetTypeEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef)
u32 AI_GetMoveEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef)
{
u32 damageVar, effectivenessMultiplier;
gMoveResultFlags = 0;
gCurrentMove = move;
effectivenessMultiplier = AI_GetTypeEffectiveness(gCurrentMove, battlerAtk, battlerDef);
return AI_GetEffectiveness(AI_GetTypeEffectiveness(move, battlerAtk, battlerDef));
}
switch (effectivenessMultiplier)
static u32 AI_GetEffectiveness(u16 multiplier)
{
switch (multiplier)
{
case UQ_4_12(0.0):
default:
damageVar = AI_EFFECTIVENESS_x0;
break;
return AI_EFFECTIVENESS_x0;
case UQ_4_12(0.125):
damageVar = AI_EFFECTIVENESS_x0_125;
break;
return AI_EFFECTIVENESS_x0_125;
case UQ_4_12(0.25):
damageVar = AI_EFFECTIVENESS_x0_25;
break;
return AI_EFFECTIVENESS_x0_25;
case UQ_4_12(0.5):
damageVar = AI_EFFECTIVENESS_x0_5;
break;
return AI_EFFECTIVENESS_x0_5;
case UQ_4_12(1.0):
damageVar = AI_EFFECTIVENESS_x1;
break;
default:
return AI_EFFECTIVENESS_x1;
case UQ_4_12(2.0):
damageVar = AI_EFFECTIVENESS_x2;
break;
return AI_EFFECTIVENESS_x2;
case UQ_4_12(4.0):
damageVar = AI_EFFECTIVENESS_x4;
break;
return AI_EFFECTIVENESS_x4;
case UQ_4_12(8.0):
damageVar = AI_EFFECTIVENESS_x8;
break;
return AI_EFFECTIVENESS_x8;
}
return damageVar;
}
/* Checks to see if AI will move ahead of another battler
@ -995,23 +1006,25 @@ u32 AI_GetMoveEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef)
* AI_IS_FASTER: is user(ai) faster
* AI_IS_SLOWER: is target faster
*/
u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2)
u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2, u16 moveConsidered)
{
u32 fasterAI = 0, fasterPlayer = 0, i;
s8 prioAI = 0;
s8 prioPlayer = 0;
s8 prioBattler2 = 0;
u16 *battler2Moves = GetMovesArray(battler2);
// Check move priorities first.
prioAI = GetMovePriority(battlerAI, AI_THINKING_STRUCT->moveConsidered);
prioAI = GetMovePriority(battlerAI, moveConsidered);
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gBattleMons[battler2].moves[i] == 0 || gBattleMons[battler2].moves[i] == 0xFFFF)
if (battler2Moves[i] == 0 || battler2Moves[i] == 0xFFFF)
continue;
prioPlayer = GetMovePriority(battler2, gBattleMons[battler2].moves[i]);
if (prioAI > prioPlayer)
prioBattler2 = GetMovePriority(battler2, battler2Moves[i]);
if (prioAI > prioBattler2)
fasterAI++;
else if (prioPlayer > prioAI)
else if (prioBattler2 > prioAI)
fasterPlayer++;
}
@ -1025,6 +1038,8 @@ u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2)
}
else
{
if (prioAI > prioBattler2)
return AI_IS_FASTER; // if we didn't know any of battler 2's moves to compare priorities, assume they don't have a prio+ move
// Priorities are the same(at least comparing to moves the AI is aware of), decide by speed.
if (GetWhoStrikesFirst(battlerAI, battler2, TRUE) == 0)
return AI_IS_FASTER;
@ -1037,13 +1052,13 @@ u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2)
bool32 CanTargetFaintAi(u8 battlerDef, u8 battlerAtk)
{
s32 i, dmg;
u32 unusable = CheckMoveLimitations(battlerDef, 0, MOVE_LIMITATIONS_ALL);
u32 unusable = AI_DATA->moveLimitations[battlerDef];
u16 *moves = gBattleResources->battleHistory->usedMoves[battlerDef];
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moves[i] != MOVE_NONE && moves[i] != 0xFFFF && !(unusable & gBitTable[i])
&& AI_CalcDamage(moves[i], battlerDef, battlerAtk) >= gBattleMons[battlerAtk].hp)
&& AI_DATA->simulatedDmg[battlerDef][battlerAtk][moves[i]] >= gBattleMons[battlerAtk].hp)
{
return TRUE;
}
@ -1057,7 +1072,7 @@ bool32 CanTargetFaintAi(u8 battlerDef, u8 battlerAtk)
bool32 CanAIFaintTarget(u8 battlerAtk, u8 battlerDef, u8 numHits)
{
s32 i, dmg;
u32 moveLimitations = CheckMoveLimitations(battlerAtk, 0, MOVE_LIMITATIONS_ALL);
u32 moveLimitations = AI_DATA->moveLimitations[battlerAtk];
u16 *moves = gBattleMons[battlerAtk].moves;
for (i = 0; i < MAX_MON_MOVES; i++)
@ -1065,7 +1080,7 @@ bool32 CanAIFaintTarget(u8 battlerAtk, u8 battlerDef, u8 numHits)
if (moves[i] != MOVE_NONE && moves[i] != 0xFFFF && !(moveLimitations & gBitTable[i]))
{
// Use the pre-calculated value in simulatedDmg instead of re-calculating it
dmg = AI_THINKING_STRUCT->simulatedDmg[battlerAtk][battlerDef][i];
dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i];
if (numHits)
dmg *= numHits;
@ -1081,9 +1096,13 @@ bool32 CanAIFaintTarget(u8 battlerAtk, u8 battlerDef, u8 numHits)
bool32 CanMoveFaintBattler(u16 move, u8 battlerDef, u8 battlerAtk, u8 nHits)
{
s32 i, dmg;
u32 unusable = CheckMoveLimitations(battlerDef, 0, MOVE_LIMITATIONS_ALL);
u8 effectiveness;
u32 unusable = AI_DATA->moveLimitations[battlerDef];
if (move != MOVE_NONE && move != 0xFFFF && !(unusable & gBitTable[i]) && AI_CalcDamage(move, battlerDef, battlerAtk) >= gBattleMons[battlerAtk].hp)
if (move != MOVE_NONE
&& move != 0xFFFF
&& !(unusable & gBitTable[i])
&& AI_CalcDamage(move, battlerDef, battlerAtk, &effectiveness) >= gBattleMons[battlerAtk].hp)
return TRUE;
return FALSE;
@ -1093,7 +1112,7 @@ bool32 CanMoveFaintBattler(u16 move, u8 battlerDef, u8 battlerAtk, u8 nHits)
bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgMod)
{
u32 i;
u32 unusable = CheckMoveLimitations(battlerDef, 0, MOVE_LIMITATIONS_ALL);
u32 unusable = AI_DATA->moveLimitations[battlerDef];
s32 dmg;
u16 *moves = gBattleResources->battleHistory->usedMoves[battlerDef];
u32 hpCheck = gBattleMons[battlerAtk].hp + hpMod;
@ -1103,7 +1122,7 @@ bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgM
for (i = 0; i < MAX_MON_MOVES; i++)
{
dmg = AI_THINKING_STRUCT->simulatedDmg[battlerAtk][battlerDef][i];
dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i];
if (dmgMod)
dmg *= dmgMod;
@ -1118,9 +1137,9 @@ bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgM
bool32 AI_IsAbilityOnSide(u32 battlerId, u32 ability)
{
if (IsBattlerAlive(battlerId) && AI_GetAbility(battlerId) == ability)
if (IsBattlerAlive(battlerId) && AI_DATA->abilities[battlerId] == ability)
return TRUE;
else if (IsBattlerAlive(BATTLE_PARTNER(battlerId)) && AI_GetAbility(BATTLE_PARTNER(battlerId)) == ability)
else if (IsBattlerAlive(BATTLE_PARTNER(battlerId)) && AI_DATA->abilities[BATTLE_PARTNER(battlerId)] == ability)
return TRUE;
else
return FALSE;
@ -1177,7 +1196,7 @@ u16 AI_GetHoldEffect(u32 battlerId)
return HOLD_EFFECT_NONE;
if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM)
return HOLD_EFFECT_NONE;
if (AI_GetAbility(battlerId) == ABILITY_KLUTZ && !(gStatuses3[battlerId] & STATUS3_GASTRO_ACID))
if (AI_DATA->abilities[battlerId] == ABILITY_KLUTZ && !(gStatuses3[battlerId] & STATUS3_GASTRO_ACID))
return HOLD_EFFECT_NONE;
return holdEffect;
@ -1195,7 +1214,7 @@ bool32 AI_IsTerrainAffected(u8 battlerId, u32 flags)
// different from IsBattlerGrounded in that we don't always know battler's hold effect or ability
bool32 AI_IsBattlerGrounded(u8 battlerId)
{
u32 holdEffect = AI_GetHoldEffect(battlerId);
u32 holdEffect = AI_DATA->holdEffects[battlerId];
if (holdEffect == HOLD_EFFECT_IRON_BALL)
return TRUE;
@ -1211,7 +1230,7 @@ bool32 AI_IsBattlerGrounded(u8 battlerId)
return FALSE;
else if (holdEffect == HOLD_EFFECT_AIR_BALLOON)
return FALSE;
else if (AI_GetAbility(battlerId) == ABILITY_LEVITATE)
else if (AI_DATA->abilities[battlerId] == ABILITY_LEVITATE)
return FALSE;
else if (IS_BATTLER_OF_TYPE(battlerId, TYPE_FLYING))
return FALSE;
@ -1246,14 +1265,7 @@ bool32 AI_WeatherHasEffect(void)
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_NEGATE_UNAWARE)
return TRUE; // AI doesn't understand weather supression (handicap)
// need to manually check since we don't necessarily know opponent ability
for (i = 0; i < gBattlersCount; i++)
{
if (IsBattlerAlive(i)
&& (AI_GetAbility(i) == ABILITY_AIR_LOCK || AI_GetAbility(i) == ABILITY_CLOUD_NINE))
return FALSE;
}
return TRUE;
return WEATHER_HAS_EFFECT; // weather damping abilities are announced
}
u32 AI_GetBattlerMoveTargetType(u8 battlerId, u16 move)
@ -1365,70 +1377,10 @@ bool32 IsMoveRedirectionPrevented(u16 move, u16 atkAbility)
return FALSE;
}
// differs from GetTotalAccuracy in that we need to check AI history for item, ability, etc
u32 AI_GetMoveAccuracy(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbility, u8 atkHoldEffect, u8 defHoldEffect, u16 move)
u32 AI_GetMoveAccuracy(u8 battlerAtk, u8 battlerDef, u16 move)
{
u32 calc, moveAcc, atkParam, defParam;
s8 buff, accStage, evasionStage;
gPotentialItemEffectBattler = battlerDef;
accStage = gBattleMons[battlerAtk].statStages[STAT_ACC];
evasionStage = gBattleMons[battlerDef].statStages[STAT_EVASION];
if (atkAbility == ABILITY_UNAWARE)
evasionStage = DEFAULT_STAT_STAGE;
if (gBattleMoves[move].flags & FLAG_STAT_STAGES_IGNORED)
evasionStage = DEFAULT_STAT_STAGE;
if (defAbility == ABILITY_UNAWARE)
accStage = DEFAULT_STAT_STAGE;
if (gBattleMons[battlerDef].status2 & STATUS2_FORESIGHT || gStatuses3[battlerDef] & STATUS3_MIRACLE_EYED)
buff = accStage;
else
buff = accStage + DEFAULT_STAT_STAGE - evasionStage;
if (buff < MIN_STAT_STAGE)
buff = MIN_STAT_STAGE;
if (buff > MAX_STAT_STAGE)
buff = MAX_STAT_STAGE;
moveAcc = gBattleMoves[move].accuracy;
// Check Thunder and Hurricane on sunny weather.
if (WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_SUN
&& (gBattleMoves[move].effect == EFFECT_THUNDER || gBattleMoves[move].effect == EFFECT_HURRICANE))
moveAcc = 50;
// Check Wonder Skin.
if (defAbility == ABILITY_WONDER_SKIN && gBattleMoves[move].power == 0)
moveAcc = 50;
calc = gAccuracyStageRatios[buff].dividend * moveAcc;
calc /= gAccuracyStageRatios[buff].divisor;
if (atkAbility == ABILITY_COMPOUND_EYES)
calc = (calc * 130) / 100; // 1.3 compound eyes boost
else if (atkAbility == ABILITY_VICTORY_STAR)
calc = (calc * 110) / 100; // 1.1 victory star boost
if (IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && GetBattlerAbility(BATTLE_PARTNER(battlerAtk)) == ABILITY_VICTORY_STAR)
calc = (calc * 110) / 100; // 1.1 ally's victory star boost
if (defAbility == ABILITY_SAND_VEIL && WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_SANDSTORM)
calc = (calc * 80) / 100; // 1.2 sand veil loss
else if (defAbility == ABILITY_SNOW_CLOAK && WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_HAIL)
calc = (calc * 80) / 100; // 1.2 snow cloak loss
else if (defAbility == ABILITY_TANGLED_FEET && gBattleMons[battlerDef].status2 & STATUS2_CONFUSION)
calc = (calc * 50) / 100; // 1.5 tangled feet loss
if (atkAbility == ABILITY_HUSTLE && IS_MOVE_PHYSICAL(move))
calc = (calc * 80) / 100; // 1.2 hustle loss
if (defHoldEffect == HOLD_EFFECT_EVASION_UP)
calc = (calc * (100 - defParam)) / 100;
if (atkHoldEffect == HOLD_EFFECT_WIDE_LENS)
calc = (calc * (100 + atkParam)) / 100;
else if (atkHoldEffect == HOLD_EFFECT_ZOOM_LENS && GetBattlerTurnOrderNum(battlerAtk) > GetBattlerTurnOrderNum(battlerDef));
calc = (calc * (100 + atkParam)) / 100;
return calc;
return GetTotalAccuracy(battlerAtk, battlerDef, move, AI_DATA->abilities[battlerAtk], AI_DATA->abilities[battlerDef],
AI_DATA->holdEffects[battlerAtk], AI_DATA->holdEffects[battlerDef]);
}
bool32 IsSemiInvulnerable(u8 battlerDef, u16 move)
@ -1456,7 +1408,7 @@ bool32 IsMoveEncouragedToHit(u8 battlerAtk, u8 battlerDef, u16 move)
if (gStatuses3[battlerDef] & STATUS3_ALWAYS_HITS || gDisableStructs[battlerDef].battlerWithSureHit == battlerAtk)
return TRUE;
if (AI_GetAbility(battlerDef) == ABILITY_NO_GUARD || AI_GetAbility(battlerAtk) == ABILITY_NO_GUARD)
if (AI_DATA->abilities[battlerDef] == ABILITY_NO_GUARD || AI_DATA->abilities[battlerAtk] == ABILITY_NO_GUARD)
return TRUE;
if (B_TOXIC_NEVER_MISS >= GEN_6 && gBattleMoves[move].effect == EFFECT_TOXIC && IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON))
@ -1481,12 +1433,13 @@ bool32 IsMoveEncouragedToHit(u8 battlerAtk, u8 battlerDef, u16 move)
return FALSE;
}
bool32 ShouldTryOHKO(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbility, u32 accuracy, u16 move)
bool32 ShouldTryOHKO(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbility, u16 move)
{
u32 holdEffect = AI_GetHoldEffect(battlerDef);
u32 holdEffect = AI_DATA->holdEffects[battlerDef];
u32 accuracy = AI_GetMoveAccuracy(battlerAtk, battlerDef, move);
gPotentialItemEffectBattler = battlerDef;
if (holdEffect == HOLD_EFFECT_FOCUS_BAND && (Random() % 100) < GetBattlerHoldEffectParam(battlerDef))
if (holdEffect == HOLD_EFFECT_FOCUS_BAND && (Random() % 100) < AI_DATA->holdEffectParams[battlerDef])
return FALSE; //probabilistically speaking, focus band should activate so dont OHKO
else if (holdEffect == HOLD_EFFECT_FOCUS_SASH && AtMaxHp(battlerDef))
return FALSE;
@ -1617,7 +1570,7 @@ void ProtectChecks(u8 battlerAtk, u8 battlerDef, u16 move, u16 predictedMove, s1
{
// TODO more sophisticated logic
u16 predictedEffect = gBattleMoves[predictedMove].effect;
u8 defAbility = AI_GetAbility(battlerDef);
u8 defAbility = AI_DATA->abilities[battlerDef];
u32 uses = gDisableStructs[battlerAtk].protectUses;
/*if (GetMoveResultFlags(predictedMove) & (MOVE_RESULT_NO_EFFECT | MOVE_RESULT_MISSED))
@ -1832,7 +1785,7 @@ bool32 ShouldLowerEvasion(u8 battlerAtk, u8 battlerDef, u16 defAbility)
bool32 CanIndexMoveFaintTarget(u8 battlerAtk, u8 battlerDef, u8 index, u8 numHits)
{
s32 dmg = AI_THINKING_STRUCT->simulatedDmg[battlerAtk][battlerDef][index];
s32 dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][index];
if (numHits)
dmg *= numHits;
@ -1926,7 +1879,7 @@ bool32 HasMoveWithLowAccuracy(u8 battlerAtk, u8 battlerDef, u8 accCheck, bool32
{
s32 i;
u16 *moves = GetMovesArray(battlerAtk);
u8 moveLimitations = CheckMoveLimitations(battlerAtk, 0, MOVE_LIMITATIONS_ALL);
u8 moveLimitations = AI_DATA->moveLimitations[battlerAtk];
for (i = 0; i < MAX_MON_MOVES; i++)
{
@ -1940,7 +1893,8 @@ bool32 HasMoveWithLowAccuracy(u8 battlerAtk, u8 battlerDef, u8 accCheck, bool32
else if ((!IS_MOVE_STATUS(moves[i]) && gBattleMoves[moves[i]].accuracy == 0)
|| AI_GetBattlerMoveTargetType(battlerAtk, moves[i]) & (MOVE_TARGET_USER | MOVE_TARGET_OPPONENTS_FIELD))
continue;
if (AI_GetMoveAccuracy(battlerAtk, battlerDef, atkAbility, defAbility, atkHoldEffect, defHoldEffect, moves[i]) <= accCheck)
if (AI_GetMoveAccuracy(battlerAtk, battlerDef, moves[i]) <= accCheck)
return TRUE;
}
}
@ -1950,7 +1904,7 @@ bool32 HasMoveWithLowAccuracy(u8 battlerAtk, u8 battlerDef, u8 accCheck, bool32
bool32 HasSleepMoveWithLowAccuracy(u8 battlerAtk, u8 battlerDef)
{
u8 moveLimitations = CheckMoveLimitations(battlerAtk, 0, MOVE_LIMITATIONS_ALL);
u8 moveLimitations = AI_DATA->moveLimitations[battlerAtk];
u32 i;
u16 *moves = GetMovesArray(battlerAtk);
@ -1961,7 +1915,7 @@ bool32 HasSleepMoveWithLowAccuracy(u8 battlerAtk, u8 battlerDef)
if (!(gBitTable[i] & moveLimitations))
{
if (gBattleMoves[moves[i]].effect == EFFECT_SLEEP
&& AI_GetMoveAccuracy(battlerAtk, battlerDef, AI_DATA->atkAbility, AI_DATA->defAbility, AI_DATA->atkHoldEffect, AI_DATA->defHoldEffect, moves[i]) < 85)
&& AI_GetMoveAccuracy(battlerAtk, battlerDef, moves[i]) < 85)
return TRUE;
}
}
@ -2276,7 +2230,7 @@ static u32 GetTrapDamage(u8 battlerId)
{
// ai has no knowledge about turns remaining
u32 damage = 0;
u32 holdEffect = AI_GetHoldEffect(gBattleStruct->wrappedBy[battlerId]);
u32 holdEffect = AI_DATA->holdEffects[gBattleStruct->wrappedBy[battlerId]];
if (gBattleMons[battlerId].status2 & STATUS2_WRAPPED)
{
if (holdEffect == HOLD_EFFECT_BINDING_BAND)
@ -2294,7 +2248,7 @@ static u32 GetPoisonDamage(u8 battlerId)
{
u32 damage = 0;
if (AI_GetAbility(battlerId) == ABILITY_POISON_HEAL)
if (AI_DATA->abilities[battlerId] == ABILITY_POISON_HEAL)
return damage;
if (gBattleMons[battlerId].status1 & STATUS1_POISON)
@ -2340,8 +2294,8 @@ static bool32 BattlerAffectedByHail(u8 battlerId, u16 ability)
static u32 GetWeatherDamage(u8 battlerId)
{
u32 ability = AI_GetAbility(battlerId);
u32 holdEffect = AI_GetHoldEffect(battlerId);
u32 ability = AI_DATA->abilities[battlerId];
u32 holdEffect = AI_DATA->holdEffects[battlerId];
u32 damage = 0;
if (!AI_WeatherHasEffect())
return 0;
@ -2375,7 +2329,7 @@ u32 GetBattlerSecondaryDamage(u8 battlerId)
{
u32 secondaryDamage;
if (AI_GetAbility(battlerId) == ABILITY_MAGIC_GUARD)
if (AI_DATA->abilities[battlerId] == ABILITY_MAGIC_GUARD)
return FALSE;
secondaryDamage = GetLeechSeedDamage(battlerId)
@ -2491,7 +2445,7 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo
/*if (IsPredictedToSwitch(battlerDef, battlerAtk) && !hasStatBoost)
return PIVOT; // Try pivoting so you can switch to a better matchup to counter your new opponent*/
if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first
if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker goes first
{
if (!CanAIFaintTarget(battlerAtk, battlerDef, 0)) // Can't KO foe otherwise
{
@ -2502,13 +2456,13 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo
return PIVOT; // Won't get the two turns, pivot
if (!IS_MOVE_STATUS(move) && (shouldSwitch
|| (AtMaxHp(battlerDef) && (AI_DATA->defHoldEffect == HOLD_EFFECT_FOCUS_SASH
|| (AtMaxHp(battlerDef) && (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_FOCUS_SASH
|| defAbility == ABILITY_STURDY || defAbility == ABILITY_MULTISCALE || defAbility == ABILITY_SHADOW_SHIELD))))
return PIVOT; // pivot to break sash/sturdy/multiscale
}
else if (!hasStatBoost)
{
if (!IS_MOVE_STATUS(move) && (AtMaxHp(battlerDef) && (AI_DATA->defHoldEffect == HOLD_EFFECT_FOCUS_SASH
if (!IS_MOVE_STATUS(move) && (AtMaxHp(battlerDef) && (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_FOCUS_SASH
|| defAbility == ABILITY_STURDY || defAbility == ABILITY_MULTISCALE || defAbility == ABILITY_SHADOW_SHIELD)))
return PIVOT; // pivot to break sash/sturdy/multiscale
@ -2519,7 +2473,7 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo
if (gSideStatuses[battlerAtk] & SIDE_STATUS_SPIKES && switchScore >= SWITCHING_INCREASE_CAN_REMOVE_HAZARDS)
return PIVOT;*/
/*if (BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->atkAbility) && switchScore >= SWITCHING_INCREASE_WALLS_FOE)
/*if (BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->abilities[battlerAtk]) && switchScore >= SWITCHING_INCREASE_WALLS_FOE)
return PIVOT;*/
/*if (IsClassDamager(class) && switchScore >= SWITCHING_INCREASE_HAS_SUPER_EFFECTIVE_MOVE)
@ -2562,7 +2516,7 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo
{
if (CanAIFaintTarget(battlerAtk, battlerDef, 0))
{
if (!BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->atkAbility))
if (!BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->abilities[battlerAtk]))
return CAN_TRY_PIVOT; // Use this move to KO if you must
}
else // Can't KO the foe
@ -2574,7 +2528,7 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo
{
if (CanAIFaintTarget(battlerAtk, battlerDef, 0))
{
if (!BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->atkAbility) // This is the only move that can KO
if (!BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->abilities[battlerAtk]) // This is the only move that can KO
&& !hasStatBoost) //You're not wasting a valuable stat boost
{
return CAN_TRY_PIVOT;
@ -2585,7 +2539,7 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo
// can knock out foe in 2 hits
if (IS_MOVE_STATUS(move) && (shouldSwitch //Damaging move
//&& (switchScore >= SWITCHING_INCREASE_RESIST_ALL_MOVES + SWITCHING_INCREASE_KO_FOE //remove hazards
|| (AI_DATA->defHoldEffect == HOLD_EFFECT_FOCUS_SASH && AtMaxHp(battlerDef))))
|| (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_FOCUS_SASH && AtMaxHp(battlerDef))))
return DONT_PIVOT; // Pivot to break the sash
else
return CAN_TRY_PIVOT;
@ -2654,7 +2608,7 @@ bool32 CanKnockOffItem(u8 battler, u16 item)
)) && GetBattlerSide(battler) == B_SIDE_PLAYER)
return FALSE;
if (AI_GetAbility(battler) == ABILITY_STICKY_HOLD)
if (AI_DATA->abilities[battler] == ABILITY_STICKY_HOLD)
return FALSE;
if (!CanBattlerGetOrLoseItem(battler, item))
@ -2701,13 +2655,13 @@ bool32 AI_CanPutToSleep(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move,
static bool32 AI_CanPoisonType(u8 battlerAttacker, u8 battlerTarget)
{
return ((AI_GetAbility(battlerAttacker) == ABILITY_CORROSION && gBattleMoves[gCurrentMove].split == SPLIT_STATUS)
return ((AI_DATA->abilities[battlerAttacker] == ABILITY_CORROSION && gBattleMoves[gCurrentMove].split == SPLIT_STATUS)
|| !(IS_BATTLER_OF_TYPE(battlerTarget, TYPE_POISON) || IS_BATTLER_OF_TYPE(battlerTarget, TYPE_STEEL)));
}
static bool32 AI_CanBePoisoned(u8 battlerAtk, u8 battlerDef)
{
u16 ability = AI_GetAbility(battlerDef);
u16 ability = AI_DATA->abilities[battlerDef];
if (!(AI_CanPoisonType(battlerAtk, battlerDef))
|| gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SAFEGUARD
@ -2746,7 +2700,7 @@ bool32 AI_CanPoison(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u16
return FALSE;
else if (defAbility != ABILITY_CORROSION && (IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON) || IS_BATTLER_OF_TYPE(battlerDef, TYPE_STEEL)))
return FALSE;
else if (IsValidDoubleBattle(battlerAtk) && AI_GetAbility(BATTLE_PARTNER(battlerDef)) == ABILITY_PASTEL_VEIL)
else if (IsValidDoubleBattle(battlerAtk) && AI_DATA->abilities[BATTLE_PARTNER(battlerDef)] == ABILITY_PASTEL_VEIL)
return FALSE;
return TRUE;
@ -2851,7 +2805,7 @@ u32 ShouldTryToFlinch(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbili
{
if (defAbility == ABILITY_INNER_FOCUS
|| DoesSubstituteBlockMove(battlerAtk, battlerDef, move)
|| AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent goes first
|| AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Opponent goes first
{
return 0; // don't try to flinch
}
@ -2871,7 +2825,7 @@ u32 ShouldTryToFlinch(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbili
bool32 ShouldTrap(u8 battlerAtk, u8 battlerDef, u16 move)
{
if (BattlerWillFaintFromSecondaryDamage(battlerDef, AI_DATA->defAbility))
if (BattlerWillFaintFromSecondaryDamage(battlerDef, AI_DATA->abilities[battlerDef]))
return TRUE; // battler is taking secondary damage with low HP
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_STALL)
@ -2885,11 +2839,11 @@ bool32 ShouldTrap(u8 battlerAtk, u8 battlerDef, u16 move)
bool32 ShouldFakeOut(u8 battlerAtk, u8 battlerDef, u16 move)
{
if (AI_DATA->atkHoldEffect == HOLD_EFFECT_CHOICE_BAND && CountUsablePartyMons(battlerAtk) == 0)
if (AI_DATA->holdEffects[battlerAtk] == HOLD_EFFECT_CHOICE_BAND && CountUsablePartyMons(battlerAtk) == 0)
return FALSE; // don't lock attacker into fake out if can't switch out
if (gDisableStructs[battlerAtk].isFirstTurn
&& ShouldTryToFlinch(battlerAtk, battlerDef, AI_DATA->atkAbility, AI_DATA->defAbility, move)
&& ShouldTryToFlinch(battlerAtk, battlerDef, AI_DATA->abilities[battlerAtk], AI_DATA->abilities[battlerDef], move)
&& !DoesSubstituteBlockMove(battlerAtk, battlerDef, move))
return TRUE;
@ -2975,7 +2929,7 @@ bool32 ShouldUseRecoilMove(u8 battlerAtk, u8 battlerDef, u32 recoilDmg, u8 moveI
bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage)
{
if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER)
if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
{
// using item or user goes first
u8 healPercent = (gBattleMoves[move].argument == 0) ? 50 : gBattleMoves[move].argument;
@ -2987,7 +2941,7 @@ bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage)
if (CanTargetFaintAi(battlerDef, battlerAtk)
&& !CanTargetFaintAiWithMod(battlerDef, battlerAtk, healDmg, 0))
return TRUE; // target can faint attacker unless they heal
else if (!CanTargetFaintAi(battlerDef, battlerAtk) && GetHealthPercentage(battlerAtk) < 60 && (Random() % 3))
else if (!CanTargetFaintAi(battlerDef, battlerAtk) && AI_DATA->hpPercents[battlerAtk] < 60 && (Random() % 3))
return TRUE; // target can't faint attacker at all, attacker health is about half, 2/3rds rate of encouraging healing
}
else
@ -3002,10 +2956,10 @@ bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage)
bool32 ShouldRecover(u8 battlerAtk, u8 battlerDef, u16 move, u8 healPercent)
{
if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER)
if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
{
// using item or user going first
s32 damage = AI_THINKING_STRUCT->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex];
s32 damage = AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex];
s32 healAmount = (healPercent * damage) / 100;
if (gStatuses3[battlerAtk] & STATUS3_HEAL_BLOCK)
healAmount = 0;
@ -3013,7 +2967,7 @@ bool32 ShouldRecover(u8 battlerAtk, u8 battlerDef, u16 move, u8 healPercent)
if (CanTargetFaintAi(battlerDef, battlerAtk)
&& !CanTargetFaintAiWithMod(battlerDef, battlerAtk, healAmount, 0))
return TRUE; // target can faint attacker unless they heal
else if (!CanTargetFaintAi(battlerDef, battlerAtk) && GetHealthPercentage(battlerAtk) < 60 && (Random() % 3))
else if (!CanTargetFaintAi(battlerDef, battlerAtk) && AI_DATA->hpPercents[battlerAtk] < 60 && (Random() % 3))
return TRUE; // target can't faint attacker at all, attacker health is about half, 2/3rds rate of encouraging healing
}
return FALSE;
@ -3056,14 +3010,14 @@ bool32 IsValidDoubleBattle(u8 battlerAtk)
return FALSE;
}
u16 GetAllyChosenMove(void)
u16 GetAllyChosenMove(u8 battlerId)
{
u8 partnerBattler = BATTLE_PARTNER(sBattler_AI);
u8 partnerBattler = BATTLE_PARTNER(battlerId);
if (!IsBattlerAlive(partnerBattler) || !IsBattlerAIControlled(partnerBattler))
return MOVE_NONE; // TODO: prediction?
else if (partnerBattler > sBattler_AI) // Battler with the lower id chooses the move first.
return MOVE_NONE;
else if (partnerBattler > battlerId) // Battler with the lower id chooses the move first.
return gLastMoves[partnerBattler];
else
return gBattleMons[partnerBattler].moves[gBattleStruct->chosenMovePositions[partnerBattler]];
}
@ -3201,7 +3155,7 @@ bool32 ShouldUseWishAromatherapy(u8 battlerAtk, u8 battlerDef, u16 move)
party = gEnemyParty;
if (CountUsablePartyMons(battlerAtk) == 0
&& (CanTargetFaintAi(battlerDef, battlerAtk) || BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->atkAbility)))
&& (CanTargetFaintAi(battlerDef, battlerAtk) || BattlerWillFaintFromSecondaryDamage(battlerAtk, AI_DATA->abilities[battlerAtk])))
return FALSE; // Don't heal if last mon and will faint
for (i = 0; i < PARTY_SIZE; i++)
@ -3258,13 +3212,14 @@ s32 AI_CalcPartyMonDamage(u16 move, u8 battlerAtk, u8 battlerDef, struct Pokemon
{
s32 dmg;
u32 i;
u8 effectiveness;
struct BattlePokemon *battleMons = Alloc(sizeof(struct BattlePokemon) * MAX_BATTLERS_COUNT);
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
battleMons[i] = gBattleMons[i];
PokemonToBattleMon(mon, &gBattleMons[battlerAtk]);
dmg = AI_CalcDamage(move, battlerAtk, battlerDef);
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness);
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
gBattleMons[i] = battleMons[i];
@ -3459,10 +3414,10 @@ bool32 IsRecycleEncouragedItem(u16 item)
#define STAT_UP_STAGE 10
void IncreaseStatUpScore(u8 battlerAtk, u8 battlerDef, u8 statId, s16 *score)
{
if (AI_DATA->atkAbility == ABILITY_CONTRARY)
if (AI_DATA->abilities[battlerAtk] == ABILITY_CONTRARY)
return;
if (GetHealthPercentage(battlerAtk) < 80 && AI_RandLessThan(128))
if (AI_DATA->hpPercents[battlerAtk] < 80 && AI_RandLessThan(128))
return;
if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
@ -3471,7 +3426,7 @@ void IncreaseStatUpScore(u8 battlerAtk, u8 battlerDef, u8 statId, s16 *score)
switch (statId)
{
case STAT_ATK:
if (HasMoveWithSplit(battlerAtk, SPLIT_PHYSICAL) && GetHealthPercentage(battlerAtk) > 40)
if (HasMoveWithSplit(battlerAtk, SPLIT_PHYSICAL) && AI_DATA->hpPercents[battlerAtk] > 40)
{
if (gBattleMons[battlerAtk].statStages[STAT_ATK] < STAT_UP_2_STAGE)
*score += 2;
@ -3483,7 +3438,7 @@ void IncreaseStatUpScore(u8 battlerAtk, u8 battlerDef, u8 statId, s16 *score)
break;
case STAT_DEF:
if ((HasMoveWithSplit(battlerDef, SPLIT_PHYSICAL)|| IS_MOVE_PHYSICAL(gLastMoves[battlerDef]))
&& GetHealthPercentage(battlerAtk) > 70)
&& AI_DATA->hpPercents[battlerAtk] > 70)
{
if (gBattleMons[battlerAtk].statStages[STAT_DEF] < STAT_UP_2_STAGE)
*score += 2; // seems better to raise def at higher HP
@ -3501,7 +3456,7 @@ void IncreaseStatUpScore(u8 battlerAtk, u8 battlerDef, u8 statId, s16 *score)
}
break;
case STAT_SPATK:
if (HasMoveWithSplit(battlerAtk, SPLIT_SPECIAL) && GetHealthPercentage(battlerAtk) > 40)
if (HasMoveWithSplit(battlerAtk, SPLIT_SPECIAL) && AI_DATA->hpPercents[battlerAtk] > 40)
{
if (gBattleMons[battlerAtk].statStages[STAT_SPATK] < STAT_UP_2_STAGE)
*score += 2;
@ -3511,7 +3466,7 @@ void IncreaseStatUpScore(u8 battlerAtk, u8 battlerDef, u8 statId, s16 *score)
break;
case STAT_SPDEF:
if ((HasMoveWithSplit(battlerDef, SPLIT_SPECIAL) || IS_MOVE_SPECIAL(gLastMoves[battlerDef]))
&& GetHealthPercentage(battlerAtk) > 70)
&& AI_DATA->hpPercents[battlerAtk] > 70)
{
if (gBattleMons[battlerAtk].statStages[STAT_SPDEF] < STAT_UP_2_STAGE)
*score += 2; // seems better to raise spdef at higher HP
@ -3520,13 +3475,13 @@ void IncreaseStatUpScore(u8 battlerAtk, u8 battlerDef, u8 statId, s16 *score)
}
break;
case STAT_ACC:
if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 80, TRUE, AI_DATA->atkAbility, AI_DATA->defAbility, AI_DATA->atkHoldEffect, AI_DATA->defHoldEffect))
if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 80, TRUE, AI_DATA->abilities[battlerAtk], AI_DATA->abilities[battlerDef], AI_DATA->holdEffects[battlerAtk], AI_DATA->holdEffects[battlerDef]))
*score += 2; // has moves with less than 80% accuracy
else if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 90, TRUE, AI_DATA->atkAbility, AI_DATA->defAbility, AI_DATA->atkHoldEffect, AI_DATA->defHoldEffect))
else if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 90, TRUE, AI_DATA->abilities[battlerAtk], AI_DATA->abilities[battlerDef], AI_DATA->holdEffects[battlerAtk], AI_DATA->holdEffects[battlerDef]))
*(score)++;
break;
case STAT_EVASION:
if (!BattlerWillFaintFromWeather(battlerAtk, AI_DATA->atkAbility))
if (!BattlerWillFaintFromWeather(battlerAtk, AI_DATA->abilities[battlerAtk]))
{
if (!GetBattlerSecondaryDamage(battlerAtk) && !(gStatuses3[battlerAtk] & STATUS3_ROOTED))
*score += 2;
@ -3542,7 +3497,7 @@ void IncreasePoisonScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score)
if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return;
if (AI_CanPoison(battlerAtk, battlerDef, AI_DATA->defAbility, move, AI_DATA->partnerMove) && GetHealthPercentage(battlerDef) > 20)
if (AI_CanPoison(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], move, AI_DATA->partnerMove) && AI_DATA->hpPercents[battlerDef] > 20)
{
if (!HasDamagingMove(battlerDef))
*score += 2;
@ -3553,7 +3508,7 @@ void IncreasePoisonScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score)
if (HasMoveEffect(battlerAtk, EFFECT_VENOSHOCK)
|| HasMoveEffect(battlerAtk, EFFECT_HEX)
|| HasMoveEffect(battlerAtk, EFFECT_VENOM_DRENCH)
|| AI_DATA->atkAbility == ABILITY_MERCILESS)
|| AI_DATA->abilities[battlerAtk] == ABILITY_MERCILESS)
*(score) += 2;
else
*(score)++;
@ -3565,7 +3520,7 @@ void IncreaseBurnScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score)
if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return;
if (AI_CanBurn(battlerAtk, battlerDef, AI_DATA->defAbility, AI_DATA->battlerAtkPartner, move, AI_DATA->partnerMove))
if (AI_CanBurn(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, AI_DATA->partnerMove))
{
(*score)++; // burning is good
if (HasMoveWithSplit(battlerDef, SPLIT_PHYSICAL))
@ -3574,7 +3529,7 @@ void IncreaseBurnScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score)
*score += 2; // burning the target to stay alive is cool
}
if (HasMoveEffect(battlerAtk, EFFECT_HEX) || HasMoveEffect(AI_DATA->battlerAtkPartner, EFFECT_HEX))
if (HasMoveEffect(battlerAtk, EFFECT_HEX) || HasMoveEffect(BATTLE_PARTNER(battlerAtk), EFFECT_HEX))
(*score)++;
}
}
@ -3584,7 +3539,7 @@ void IncreaseParalyzeScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score)
if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return;
if (AI_CanParalyze(battlerAtk, battlerDef, AI_DATA->defAbility, move, AI_DATA->partnerMove))
if (AI_CanParalyze(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], move, AI_DATA->partnerMove))
{
u8 atkSpeed = GetBattlerTotalSpeedStat(battlerAtk);
u8 defSpeed = GetBattlerTotalSpeedStat(battlerDef);
@ -3605,7 +3560,7 @@ void IncreaseSleepScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score)
if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return;
if (AI_CanPutToSleep(battlerAtk, battlerDef, AI_DATA->defAbility, move, AI_DATA->partnerMove))
if (AI_CanPutToSleep(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], move, AI_DATA->partnerMove))
*score += 2;
else
return;
@ -3614,7 +3569,7 @@ void IncreaseSleepScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score)
&& !(HasMoveEffect(battlerDef, EFFECT_SNORE) || HasMoveEffect(battlerDef, EFFECT_SLEEP_TALK)))
(*score)++;
if (HasMoveEffect(battlerAtk, EFFECT_HEX) || HasMoveEffect(AI_DATA->battlerAtkPartner, EFFECT_HEX))
if (HasMoveEffect(battlerAtk, EFFECT_HEX) || HasMoveEffect(BATTLE_PARTNER(battlerAtk), EFFECT_HEX))
(*score)++;
}
@ -3623,13 +3578,13 @@ void IncreaseConfusionScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score)
if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return;
if (AI_CanConfuse(battlerAtk, battlerDef, AI_DATA->defAbility, AI_DATA->battlerAtkPartner, move, AI_DATA->partnerMove)
&& AI_DATA->defHoldEffect != HOLD_EFFECT_CURE_CONFUSION
&& AI_DATA->defHoldEffect != HOLD_EFFECT_CURE_STATUS)
if (AI_CanConfuse(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, AI_DATA->partnerMove)
&& AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_CURE_CONFUSION
&& AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_CURE_STATUS)
{
if (gBattleMons[battlerDef].status1 & STATUS1_PARALYSIS
|| gBattleMons[battlerDef].status2 & STATUS2_INFATUATION
|| (AI_DATA->atkAbility == ABILITY_SERENE_GRACE && HasMoveEffect(battlerAtk, EFFECT_FLINCH_HIT)))
|| (AI_DATA->abilities[battlerAtk] == ABILITY_SERENE_GRACE && HasMoveEffect(battlerAtk, EFFECT_FLINCH_HIT)))
*score += 3;
else
*score += 2;

View file

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

View file

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

View file

@ -714,7 +714,7 @@ void CB2_BattleDebugMenu(void)
static void PutMovesPointsText(struct BattleDebugMenu *data)
{
u32 i, j, count;
u32 i, j, count, battlerDef;
u8 *text = malloc(0x50);
FillWindowPixelBuffer(data->aiMovesWindowId, 0x11);
@ -727,13 +727,14 @@ static void PutMovesPointsText(struct BattleDebugMenu *data)
{
if (data->aiIconSpriteIds[j] == 0xFF)
continue;
battlerDef = gSprites[data->aiIconSpriteIds[j]].data[0];
ConvertIntToDecimalStringN(text,
gBattleStruct->aiFinalScore[data->aiBattlerId][gSprites[data->aiIconSpriteIds[j]].data[0]][i],
gBattleStruct->aiFinalScore[data->aiBattlerId][battlerDef][i],
STR_CONV_MODE_RIGHT_ALIGN, 3);
AddTextPrinterParameterized(data->aiMovesWindowId, 1, text, 83 + count * 54, i * 15, 0, NULL);
ConvertIntToDecimalStringN(text,
gBattleStruct->aiSimulatedDamage[data->aiBattlerId][gSprites[data->aiIconSpriteIds[j]].data[0]][i],
AI_DATA->simulatedDmg[data->aiBattlerId][battlerDef][i],
STR_CONV_MODE_RIGHT_ALIGN, 3);
AddTextPrinterParameterized(data->aiMovesWindowId, 1, text, 110 + count * 54, i * 15, 0, NULL);

View file

@ -3710,6 +3710,8 @@ static void TryDoEventsBeforeFirstTurn(void)
gMoveResultFlags = 0;
gRandomTurnNumber = Random();
GetAiLogicData(); // get assumed abilities, hold effects, etc of all battlers
if (gBattleTypeFlags & BATTLE_TYPE_ARENA)
{
@ -3798,6 +3800,7 @@ void BattleTurnPassed(void)
*(&gBattleStruct->absentBattlerFlags) = gAbsentBattlerFlags;
BattlePutTextOnWindow(gText_EmptyString3, B_WIN_MSG);
GetAiLogicData(); // get assumed abilities, hold effects, etc of all battlers
gBattleMainFunc = HandleTurnActionSelectionState;
gRandomTurnNumber = Random();
@ -3915,6 +3918,11 @@ static void HandleTurnActionSelectionState(void)
case STATE_TURN_START_RECORD: // Recorded battle related action on start of every turn.
RecordedBattle_CopyBattlerMoves();
gBattleCommunication[gActiveBattler] = STATE_BEFORE_ACTION_CHOSEN;
// Do AI score computations here so we can use them in AI_TrySwitchOrUseItem
if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart()) && IsBattlerAIControlled(gActiveBattler)) {
gBattleStruct->aiMoveOrAction[gActiveBattler] = ComputeBattleAiScores(gActiveBattler);
}
break;
case STATE_BEFORE_ACTION_CHOSEN: // Choose an action.
*(gBattleStruct->monToSwitchIntoId + gActiveBattler) = PARTY_SIZE;

View file

@ -1644,20 +1644,14 @@ static bool32 AccuracyCalcHelper(u16 move)
return FALSE;
}
u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move)
u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect)
{
u32 calc, moveAcc, atkHoldEffect, atkParam, defHoldEffect, defParam, atkAbility, defAbility;
u32 calc, moveAcc;
s8 buff, accStage, evasionStage;
u8 atkParam = GetBattlerHoldEffectParam(battlerAtk);
u8 defParam = GetBattlerHoldEffectParam(battlerDef);
atkAbility = GetBattlerAbility(battlerAtk);
atkHoldEffect = GetBattlerHoldEffect(battlerAtk, TRUE);
atkParam = GetBattlerHoldEffectParam(battlerAtk);
defAbility = GetBattlerAbility(battlerDef);
defHoldEffect = GetBattlerHoldEffect(battlerDef, TRUE);
defParam = GetBattlerHoldEffectParam(battlerDef);
gPotentialItemEffectBattler = battlerDef;
accStage = gBattleMons[battlerAtk].statStages[STAT_ACC];
evasionStage = gBattleMons[battlerDef].statStages[STAT_EVASION];
if (atkAbility == ABILITY_UNAWARE || atkAbility == ABILITY_KEEN_EYE)
@ -1755,7 +1749,8 @@ static void Cmd_accuracycheck(void)
return;
// final calculation
if ((Random() % 100 + 1) > GetTotalAccuracy(gBattlerAttacker, gBattlerTarget, move))
if ((Random() % 100 + 1) > GetTotalAccuracy(gBattlerAttacker, gBattlerTarget, move, GetBattlerAbility(gBattlerAttacker), GetBattlerAbility(gBattlerTarget),
GetBattlerHoldEffect(gBattlerAttacker, TRUE), GetBattlerHoldEffect(gBattlerTarget, TRUE)))
{
gMoveResultFlags |= MOVE_RESULT_MISSED;
if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_BLUNDER_POLICY)

View file

@ -4463,7 +4463,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move
BattleScriptPushCursorAndCallback(BattleScript_DrizzleActivates);
effect++;
}
else if (WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_PRIMAL_ANY && !gSpecialStatuses[battler].switchInAbilityDone)
else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && WEATHER_HAS_EFFECT && !gSpecialStatuses[battler].switchInAbilityDone)
{
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
BattleScriptPushCursorAndCallback(BattleScript_BlockedByPrimalWeatherEnd3);
@ -4476,7 +4476,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move
BattleScriptPushCursorAndCallback(BattleScript_SandstreamActivates);
effect++;
}
else if (WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_PRIMAL_ANY && !gSpecialStatuses[battler].switchInAbilityDone)
else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && WEATHER_HAS_EFFECT && !gSpecialStatuses[battler].switchInAbilityDone)
{
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
BattleScriptPushCursorAndCallback(BattleScript_BlockedByPrimalWeatherEnd3);
@ -4489,7 +4489,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move
BattleScriptPushCursorAndCallback(BattleScript_DroughtActivates);
effect++;
}
else if (WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_PRIMAL_ANY && !gSpecialStatuses[battler].switchInAbilityDone)
else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && WEATHER_HAS_EFFECT && !gSpecialStatuses[battler].switchInAbilityDone)
{
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
BattleScriptPushCursorAndCallback(BattleScript_BlockedByPrimalWeatherEnd3);
@ -4502,7 +4502,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move
BattleScriptPushCursorAndCallback(BattleScript_SnowWarningActivates);
effect++;
}
else if (WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_PRIMAL_ANY && !gSpecialStatuses[battler].switchInAbilityDone)
else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && WEATHER_HAS_EFFECT && !gSpecialStatuses[battler].switchInAbilityDone)
{
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
BattleScriptPushCursorAndCallback(BattleScript_BlockedByPrimalWeatherEnd3);
@ -5387,9 +5387,9 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move
if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
&& TARGET_TURN_DAMAGED
&& !(WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_SANDSTORM))
&& !(gBattleWeather & B_WEATHER_SANDSTORM && WEATHER_HAS_EFFECT))
{
if (WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_PRIMAL_ANY)
if (gBattleWeather & B_WEATHER_PRIMAL_ANY && WEATHER_HAS_EFFECT)
{
BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_BlockedByPrimalWeatherRet;
@ -8069,7 +8069,7 @@ static u16 CalcMoveBasePower(u16 move, u8 battlerAtk, u8 battlerDef)
basePower *= 2;
break;
case EFFECT_WEATHER_BALL:
if (WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_ANY)
if (gBattleWeather & B_WEATHER_ANY && WEATHER_HAS_EFFECT)
basePower *= 2;
break;
case EFFECT_PURSUIT:
@ -8280,7 +8280,7 @@ static u32 CalcMoveBasePowerAfterModifiers(u16 move, u8 battlerAtk, u8 battlerDe
break;
case ABILITY_SAND_FORCE:
if ((moveType == TYPE_STEEL || moveType == TYPE_ROCK || moveType == TYPE_GROUND)
&& WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_SANDSTORM)
&& gBattleWeather & B_WEATHER_SANDSTORM && WEATHER_HAS_EFFECT)
MulModifier(&modifier, UQ_4_12(1.3));
break;
case ABILITY_RIVALRY:
@ -8889,7 +8889,7 @@ static u32 CalcDefenseStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType,
}
// sandstorm sp.def boost for rock types
if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_ROCK) && WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_SANDSTORM && !usesDefStat)
if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_ROCK) && gBattleWeather & B_WEATHER_SANDSTORM && WEATHER_HAS_EFFECT && !usesDefStat)
MulModifier(&modifier, UQ_4_12(1.5));
// The defensive stats of a Player's Pokémon are boosted by x1.1 (+10%) if they have the 5th badge and 7th badges.
@ -9062,13 +9062,11 @@ static u32 CalcFinalDmg(u32 dmg, u16 move, u8 battlerAtk, u8 battlerDef, u8 move
return dmg;
}
s32 CalculateMoveDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, s32 fixedBasePower, bool32 isCrit, bool32 randomFactor, bool32 updateFlags)
static s32 DoMoveDamageCalc(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, s32 fixedBasePower,
bool32 isCrit, bool32 randomFactor, bool32 updateFlags, u16 typeEffectivenessModifier)
{
s32 dmg;
u16 typeEffectivenessModifier;
typeEffectivenessModifier = CalcTypeEffectivenessMultiplier(move, moveType, battlerAtk, battlerDef, updateFlags);
// Don't calculate damage if the move has no effect on target.
if (typeEffectivenessModifier == UQ_4_12(0))
return 0;
@ -9101,6 +9099,19 @@ s32 CalculateMoveDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, s32
return dmg;
}
s32 CalculateMoveDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, s32 fixedBasePower, bool32 isCrit, bool32 randomFactor, bool32 updateFlags)
{
return DoMoveDamageCalc(move, battlerAtk, battlerDef, moveType, fixedBasePower, isCrit, randomFactor,
updateFlags, CalcTypeEffectivenessMultiplier(move, moveType, battlerAtk, battlerDef, updateFlags));
}
// for AI - get move damage and effectiveness with one function call
s32 CalculateMoveDamageAndEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, u16 *typeEffectivenessModifier)
{
*typeEffectivenessModifier = CalcTypeEffectivenessMultiplier(move, moveType, battlerAtk, battlerDef, FALSE);
return DoMoveDamageCalc(move, battlerAtk, battlerDef, moveType, 0, FALSE, FALSE, FALSE, *typeEffectivenessModifier);
}
static void MulByTypeEffectiveness(u16 *modifier, u16 move, u8 moveType, u8 battlerDef, u8 defType, u8 battlerAtk, bool32 recordAbilities)
{
u16 mod = GetTypeModifier(moveType, defType);
@ -9132,7 +9143,7 @@ static void MulByTypeEffectiveness(u16 *modifier, u16 move, u8 moveType, u8 batt
mod = UQ_4_12(2.0);
// B_WEATHER_STRONG_WINDS weakens Super Effective moves against Flying-type Pokémon
if (WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_STRONG_WINDS)
if (gBattleWeather & B_WEATHER_STRONG_WINDS && WEATHER_HAS_EFFECT)
{
if (defType == TYPE_FLYING && mod >= UQ_4_12(2.0))
mod = UQ_4_12(1.0);
@ -10089,10 +10100,7 @@ u16 GetUsedHeldItem(u8 battler)
bool32 IsBattlerWeatherAffected(u8 battlerId, u32 weatherFlags)
{
if (!WEATHER_HAS_EFFECT)
return FALSE;
if (gBattleWeather & weatherFlags)
if (gBattleWeather & weatherFlags && WEATHER_HAS_EFFECT)
{
// given weather is active -> check if its sun, rain against utility umbrella ( since only 1 weather can be active at once)
if (gBattleWeather & (B_WEATHER_SUN | B_WEATHER_RAIN) && GetBattlerHoldEffect(battlerId, TRUE) == HOLD_EFFECT_UTILITY_UMBRELLA)

View file

@ -26,6 +26,7 @@ void AllocateBattleResources(void)
gBattleResources->battleCallbackStack = AllocZeroed(sizeof(*gBattleResources->battleCallbackStack));
gBattleResources->beforeLvlUp = AllocZeroed(sizeof(*gBattleResources->beforeLvlUp));
gBattleResources->ai = AllocZeroed(sizeof(*gBattleResources->ai));
gBattleResources->aiData = AllocZeroed(sizeof(*gBattleResources->aiData));
gBattleResources->battleHistory = AllocZeroed(sizeof(*gBattleResources->battleHistory));
gLinkBattleSendBuffer = AllocZeroed(BATTLE_BUFFER_LINK_SIZE);
@ -57,6 +58,7 @@ void FreeBattleResources(void)
FREE_AND_SET_NULL(gBattleResources->battleCallbackStack);
FREE_AND_SET_NULL(gBattleResources->beforeLvlUp);
FREE_AND_SET_NULL(gBattleResources->ai);
FREE_AND_SET_NULL(gBattleResources->aiData);
FREE_AND_SET_NULL(gBattleResources->battleHistory);
FREE_AND_SET_NULL(gBattleResources);