outline AI_CheckGoodMove

This commit is contained in:
Evan 2020-12-15 21:57:33 -07:00
parent ac332d5e98
commit 916d0416e3
12 changed files with 961 additions and 461 deletions

View File

@ -7,9 +7,6 @@
#define AI_CHOICE_WATCH 5
#define AI_CHOICE_SWITCH 7
s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef);
s32 AI_CalcPartyMonDamage(u16 move, u8 battlerAtk, u8 battlerDef, struct Pokemon *mon);
u16 AI_GetTypeEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef);
void BattleAI_SetupItems(void);
void BattleAI_SetupFlags(void);
void BattleAI_SetupAIData(u8 defaultScoreMoves);
@ -17,12 +14,6 @@ u8 BattleAI_ChooseMoveOrAction(void);
bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler);
bool32 IsBattlerAIControlled(u32 battlerId);
void ClearBattlerMoveHistory(u8 battlerId);
void RecordLastUsedMoveBy(u32 battlerId, u32 move);
void RecordKnownMove(u8 battlerId, u32 move);
void RecordAbilityBattle(u8 battlerId, u16 abilityId);
void ClearBattlerAbilityHistory(u8 battlerId);
void RecordItemEffectBattle(u8 battlerId, u8 itemEffect);
void ClearBattlerItemEffectHistory(u8 battlerId);
extern u8 sBattler_AI;

View File

@ -24,14 +24,13 @@ u32 GetHealthPercentage(u8 battler);
bool32 IsBattlerTrapped(u8 battler, bool8 switching);
bool32 IsBattlerFaster(u8 battler);
bool32 CanTargetFaintAi(u8 battlerDef, u8 battlerAtk);
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);
bool32 DoesBattlerIgnoreAbilityChecks(u16 atkAbility, u16 move);
bool32 AI_WeatherHasEffect(void);
bool32 CanAttackerFaintTarget(u8 battlerAtk, u8 battlerDef, u8 index);
s32 CountUsablePartyMons(u8 battlerId);
bool32 IsPartyFullyHealedExceptBattler(u8 battler);
bool32 AI_IsBattlerGrounded(u8 battlerId);
bool32 BattlerHasDamagingMove(u8 battlerId);
bool32 BattlerHasSecondaryDamage(u8 battlerId);
@ -39,6 +38,7 @@ bool32 BattlerWillFaintFromWeather(u8 battler, u16 ability);
bool32 ShouldTryOHKO(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbility, u32 accuracy, u16 move);
bool32 ShouldUseRecoilMove(u8 battlerAtk, u8 battlerDef, u32 recoilDmg, u8 moveIndex);
u16 GetBattlerSideSpeedAverage(u8 battler);
bool32 ShouldRecover(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage);
// stat stage checks
bool32 AnyStatIsRaised(u8 battlerId);
@ -51,13 +51,15 @@ u32 CountNegativeStatStages(u8 battlerId);
bool32 BattlerShouldRaiseAttacks(u8 battlerId, u16 ability);
// move checks
u8 GetMovePowerResult(u16 move);
s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef);
u8 GetMoveDamageResult(u16 move);
u16 AI_GetTypeEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef);
u8 AI_GetMoveEffectiveness(u16 move);
u16 *GetMovesArray(u32 battler);
bool32 IsConfusionMoveEffect(u16 moveEffect);
bool32 HasMoveWithSplit(u32 battler, u32 split);
bool32 HasMoveWithType(u32 battler, u8 type);
bool32 HasMoveEffect(u32 battlerId, u16 moveEffect);
bool32 TestMoveFlagsInMoveset(u8 battler, u32 flags);
bool32 IsAromaVeilProtectedMove(u16 move);
bool32 IsNonVolatileStatusMoveEffect(u16 moveEffect);
@ -70,13 +72,13 @@ bool32 MoveRequiresRecharging(u16 move);
bool32 IsInstructBannedMove(u16 move);
// status checks
bool32 AI_ShouldPutToSleep(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u16 partnerMove);
bool32 AI_ShouldPoison(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u16 partnerMove);
bool32 AI_CanPutToSleep(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u16 partnerMove);
bool32 AI_CanPoison(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u16 partnerMove);
bool32 AI_CanParalyze(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u16 partnerMove);
bool32 AnyPartyMemberStatused(u8 battlerId, bool32 checkSoundproof);
bool32 AI_CanConfuse(u8 battlerAtk, u8 battlerDef, u16 defAbility, u8 battlerAtkPartner, u16 move, u16 partnerMove);
bool32 AI_CanBurn(u8 battlerAtk, u8 battlerDef, u16 defAbility, u8 battlerAtkPartner, u16 move, u16 partnerMove);
bool32 AI_CanBeInfatuated(u8 battlerAtk, u8 battlerDef, u16 defAbility, u8 atkGender, u8 defGender);
bool32 AnyPartyMemberStatused(u8 battlerId, bool32 checkSoundproof);
// partner logic
u16 GetAllyChosenMove(void);
@ -91,4 +93,9 @@ bool32 PartnerMoveIs(u8 battlerAtkPartner, u16 partnerMove, u16 moveCheck);
bool32 PartnerMoveIsSameAsAttacker(u8 battlerAtkPartner, u8 battlerDef, u16 move, u16 partnerMove);
bool32 PartnerMoveIsSameNoTarget(u8 battlerAtkPartner, u16 move, u16 partnerMove);
// party logic
s32 AI_CalcPartyMonDamage(u16 move, u8 battlerAtk, u8 battlerDef, struct Pokemon *mon);
s32 CountUsablePartyMons(u8 battlerId);
bool32 IsPartyFullyHealedExceptBattler(u8 battler);
#endif //GUARD_BATTLE_AI_UTIL_H

View File

@ -38,7 +38,7 @@
// AI Flags. Most run specific functions to update score, new flags are used for internal logic in other scripts
#define AI_FLAG_CHECK_BAD_MOVE (1 << 0)
#define AI_FLAG_TRY_TO_FAINT (1 << 1)
#define AI_FLAG_CHECK_VIABILITY (1 << 2)
#define AI_FLAG_CHECK_GOOD_MOVE (1 << 2) // was AI_SCRIPT_CHECK_VIABILITY
#define AI_FLAG_SETUP_FIRST_TURN (1 << 3)
#define AI_FLAG_RISKY (1 << 4)
#define AI_FLAG_PREFER_STRONGEST_MOVE (1 << 5)
@ -49,6 +49,7 @@
#define AI_FLAG_NEGATE_AWARE (1 << 9) // AI is aware of negating effects like wonder room, mold breaker, etc (eg. smart trainers). TODO unfinished
#define AI_FLAG_HELP_PARTNER (1 << 10) // AI can try to help partner. If not set, will tend not to target partner
#define AI_FLAG_WILL_SUICIDE (1 << 11) // AI will use explosion / self destruct / final gambit / etc
#define AI_FLAG_PREFER_STATUS_MOVES (1 << 12) // AI gets a score bonus for status moves. Should be combined with AI_FLAG_CHECK_BAD_MOVE to prevent using only status moves
// 'other' ai logic flags
#define AI_FLAG_ROAMING (1 << 29)

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
#include "global.h"
#include "battle.h"
#include "battle_ai_script_commands.h"
#include "battle_ai_util.h"
#include "battle_anim.h"
#include "battle_controllers.h"
#include "battle_setup.h"

View File

@ -266,6 +266,63 @@ bool32 IsBattlerTrapped(u8 battler, bool8 checkSwitch)
return FALSE;
}
// move checks
static bool32 AI_GetIfCrit(u32 move, u8 battlerAtk, u8 battlerDef)
{
bool32 isCrit;
switch (CalcCritChanceStage(battlerAtk, battlerDef, move, FALSE))
{
case -1:
case 0:
default:
isCrit = FALSE;
break;
case 1:
if (gBattleMoves[move].flags & FLAG_HIGH_CRIT && (Random() % 5 == 0))
isCrit = TRUE;
else
isCrit = FALSE;
break;
case 2:
if (gBattleMoves[move].flags & FLAG_HIGH_CRIT && (Random() % 2 == 0))
isCrit = TRUE;
else if (!(gBattleMoves[move].flags & FLAG_HIGH_CRIT) && (Random() % 4) == 0)
isCrit = TRUE;
else
isCrit = FALSE;
break;
case -2:
case 3:
case 4:
isCrit = TRUE;
break;
}
return isCrit;
}
s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef)
{
s32 dmg, moveType;
SaveBattlerData(battlerAtk);
SaveBattlerData(battlerDef);
SetBattlerData(battlerAtk);
SetBattlerData(battlerDef);
gBattleStruct->dynamicMoveType = 0;
SetTypeBeforeUsingMove(move, battlerAtk);
GET_MOVE_TYPE(move, moveType);
dmg = CalculateMoveDamage(move, battlerAtk, battlerDef, moveType, 0, AI_GetIfCrit(move, battlerAtk, battlerDef), FALSE, FALSE);
RestoreBattlerData(battlerAtk);
RestoreBattlerData(battlerDef);
return dmg;
}
// Checks if one of the moves has side effects or perks
static u32 WhichMoveBetter(u32 move1, u32 move2)
{
@ -324,7 +381,7 @@ static u32 WhichMoveBetter(u32 move1, u32 move2)
return 2;
}
u8 GetMovePowerResult(u16 move)
u8 GetMoveDamageResult(u16 move)
{
s32 i, checkedMove, bestId, currId, hp;
s32 moveDmgs[MAX_MON_MOVES];
@ -458,8 +515,8 @@ u8 AI_GetMoveEffectiveness(u16 move)
return damageVar;
}
// 0: is user(ai) faster
// 1: is target faster
// AI_CHECK_FASTER: is user(ai) faster
// AI_CHECK_SLOWER: is target faster
bool32 IsBattlerFaster(u8 battler)
{
u32 fasterAI = 0, fasterPlayer = 0, i;
@ -525,6 +582,26 @@ bool32 CanTargetFaintAi(u8 battlerDef, u8 battlerAtk)
return FALSE;
}
// Check if target has means to faint ai mon after modding hp/dmg
bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgMod)
{
u32 i;
u32 unusable = CheckMoveLimitations(battlerDef, 0, 0xFF & ~MOVE_LIMITATION_PP);
u16 *moves = gBattleResources->battleHistory->usedMoves[battlerDef];
for (i = 0; i < MAX_MON_MOVES; i++)
{
u32 dmg = AI_CalcDamage(moves[i], battlerDef, battlerAtk) + dmgMod;
u32 hpCheck = gBattleMons[battlerAtk].hp + hpMod;
if (moves[i] != MOVE_NONE && moves[i] != 0xFFFF && !(unusable & gBitTable[i]) && dmg >= hpCheck)
{
return TRUE;
}
}
return FALSE;
}
// does NOT include ability suppression checks
s32 AI_GetAbility(u32 battlerId)
{
@ -954,42 +1031,6 @@ bool32 CanAttackerFaintTarget(u8 battlerAtk, u8 battlerDef, u8 index)
return FALSE;
}
s32 CountUsablePartyMons(u8 battlerId)
{
s32 battlerOnField1, battlerOnField2, i, ret;
struct Pokemon *party;
if (GetBattlerSide(battlerId) == B_SIDE_PLAYER)
party = gPlayerParty;
else
party = gEnemyParty;
if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
{
battlerOnField1 = gBattlerPartyIndexes[battlerId];
battlerOnField2 = gBattlerPartyIndexes[GetBattlerAtPosition(GetBattlerPosition(battlerId) ^ BIT_FLANK)];
}
else // In singles there's only one battlerId by side.
{
battlerOnField1 = gBattlerPartyIndexes[battlerId];
battlerOnField2 = gBattlerPartyIndexes[battlerId];
}
ret = 0;
for (i = 0; i < PARTY_SIZE; i++)
{
if (i != battlerOnField1 && i != battlerOnField2
&& GetMonData(&party[i], MON_DATA_HP) != 0
&& GetMonData(&party[i], MON_DATA_SPECIES2) != SPECIES_NONE
&& GetMonData(&party[i], MON_DATA_SPECIES2) != SPECIES_EGG)
{
ret++;
}
}
return ret;
}
u16 *GetMovesArray(u32 battler)
{
if (IsBattlerAIControlled(battler) || IsBattlerAIControlled(BATTLE_PARTNER(battler)))
@ -1026,6 +1067,20 @@ bool32 HasMoveWithType(u32 battler, u8 type)
return FALSE;
}
bool32 HasMoveEffect(u32 battlerId, u16 moveEffect)
{
s32 i;
u16 *moves = GetMovesArray(battlerId);
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moves[i] != MOVE_NONE && moves[i] != 0xFFFF && gBattleMoves[moves[i]].effect == moveEffect)
return TRUE;
}
return FALSE;
}
bool32 IsInstructBannedMove(u16 move)
{
u32 i;
@ -1251,7 +1306,7 @@ bool32 BattlerWillFaintFromWeather(u8 battler, u16 ability)
}
// status checks
bool32 AI_ShouldPutToSleep(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u16 partnerMove)
bool32 AI_CanPutToSleep(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u16 partnerMove)
{
if (defAbility == ABILITY_INSOMNIA
|| defAbility == ABILITY_VITAL_SPIRIT
@ -1264,7 +1319,7 @@ bool32 AI_ShouldPutToSleep(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 mov
return TRUE;
}
bool32 AI_ShouldPoison(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u16 partnerMove)
bool32 AI_CanPoison(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u16 partnerMove)
{
if (defAbility == ABILITY_IMMUNITY
|| defAbility == ABILITY_PASTEL_VEIL
@ -1358,28 +1413,6 @@ bool32 AnyPartyMemberStatused(u8 battlerId, bool32 checkSoundproof)
return FALSE;
}
bool32 IsPartyFullyHealedExceptBattler(u8 battlerId)
{
struct Pokemon *party;
u32 i;
if (GetBattlerSide(battlerId) == B_SIDE_PLAYER)
party = gPlayerParty;
else
party = gEnemyParty;
for (i = 0; i < PARTY_SIZE; i++)
{
if (i != gBattlerPartyIndexes[battlerId]
&& GetMonData(&party[i], MON_DATA_HP) != 0
&& GetMonData(&party[i], MON_DATA_SPECIES2) != SPECIES_NONE
&& GetMonData(&party[i], MON_DATA_SPECIES2) != SPECIES_EGG
&& GetMonData(&party[i], MON_DATA_HP) < GetMonData(&party[i], MON_DATA_MAX_HP))
return FALSE;
}
return TRUE;
}
u16 GetBattlerSideSpeedAverage(u8 battler)
{
u16 speed1 = 0;
@ -1415,6 +1448,30 @@ bool32 ShouldUseRecoilMove(u8 battlerAtk, u8 battlerDef, u32 recoilDmg, u8 moveI
return TRUE;
}
bool32 ShouldRecover(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage)
{
if (move == 0xFFFF || GetWhoStrikesFirst(sBattler_AI, gBattlerTarget, TRUE) == 0)
{
// using item or user goes first
u8 healPercent = (gBattleMoves[move].argument == 0) ? 50 : gBattleMoves[move].argument;
s32 healDmg = (healPercent * damage) / 100;
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))
return TRUE; // target can't faint attacker at all, attacker health is about half, 2/3rds rate of encouraging healing
}
else
{
// opponent goes first
if (!CanTargetFaintAi(battlerDef, battlerAtk))
return TRUE;
}
return FALSE;
}
// Partner Logic
bool32 IsValidDoubleBattle(u8 battlerAtk)
{
@ -1556,3 +1613,82 @@ bool32 PartnerMoveIsSameNoTarget(u8 battlerAtkPartner, u16 move, u16 partnerMove
return FALSE;
}
// party logic
s32 AI_CalcPartyMonDamage(u16 move, u8 battlerAtk, u8 battlerDef, struct Pokemon *mon)
{
s32 dmg;
u32 i;
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);
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
gBattleMons[i] = battleMons[i];
Free(battleMons);
return dmg;
}
s32 CountUsablePartyMons(u8 battlerId)
{
s32 battlerOnField1, battlerOnField2, i, ret;
struct Pokemon *party;
if (GetBattlerSide(battlerId) == B_SIDE_PLAYER)
party = gPlayerParty;
else
party = gEnemyParty;
if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
{
battlerOnField1 = gBattlerPartyIndexes[battlerId];
battlerOnField2 = gBattlerPartyIndexes[GetBattlerAtPosition(GetBattlerPosition(battlerId) ^ BIT_FLANK)];
}
else // In singles there's only one battlerId by side.
{
battlerOnField1 = gBattlerPartyIndexes[battlerId];
battlerOnField2 = gBattlerPartyIndexes[battlerId];
}
ret = 0;
for (i = 0; i < PARTY_SIZE; i++)
{
if (i != battlerOnField1 && i != battlerOnField2
&& GetMonData(&party[i], MON_DATA_HP) != 0
&& GetMonData(&party[i], MON_DATA_SPECIES2) != SPECIES_NONE
&& GetMonData(&party[i], MON_DATA_SPECIES2) != SPECIES_EGG)
{
ret++;
}
}
return ret;
}
bool32 IsPartyFullyHealedExceptBattler(u8 battlerId)
{
struct Pokemon *party;
u32 i;
if (GetBattlerSide(battlerId) == B_SIDE_PLAYER)
party = gPlayerParty;
else
party = gEnemyParty;
for (i = 0; i < PARTY_SIZE; i++)
{
if (i != gBattlerPartyIndexes[battlerId]
&& GetMonData(&party[i], MON_DATA_HP) != 0
&& GetMonData(&party[i], MON_DATA_SPECIES2) != SPECIES_NONE
&& GetMonData(&party[i], MON_DATA_SPECIES2) != SPECIES_EGG
&& GetMonData(&party[i], MON_DATA_HP) < GetMonData(&party[i], MON_DATA_MAX_HP))
return FALSE;
}
return TRUE;
}

View File

@ -850,13 +850,13 @@ u32 GetAiScriptsInBattleFactory(void)
int challengeNum = gSaveBlock2Ptr->frontier.factoryWinStreaks[battleMode][lvlMode] / 7;
if (gTrainerBattleOpponent_A == TRAINER_FRONTIER_BRAIN)
return AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY;
return AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_GOOD_MOVE;
else if (challengeNum < 2)
return 0;
else if (challengeNum < 4)
return AI_FLAG_CHECK_BAD_MOVE;
else
return AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY;
return AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_GOOD_MOVE;
}
}

View File

@ -2,6 +2,7 @@
#include "battle.h"
#include "battle_anim.h"
#include "battle_ai_script_commands.h"
#include "battle_ai_util.h"
#include "battle_arena.h"
#include "battle_controllers.h"
#include "battle_interface.h"

View File

@ -5,6 +5,7 @@
#include "battle_message.h"
#include "battle_anim.h"
#include "battle_ai_script_commands.h"
#include "battle_ai_util.h"
#include "battle_scripts.h"
#include "constants/moves.h"
#include "constants/abilities.h"

View File

@ -24,6 +24,7 @@
#include "window.h"
#include "battle_message.h"
#include "battle_ai_script_commands.h"
#include "battle_ai_util.h"
#include "event_data.h"
#include "link.h"
#include "malloc.h"

File diff suppressed because it is too large Load Diff

View File

@ -897,7 +897,7 @@ void FillHillTrainersParties(void)
// hill trainers.
u32 GetTrainerHillAIFlags(void)
{
return (AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY);
return (AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_GOOD_MOVE);
}
u8 GetTrainerEncounterMusicIdInTrainerHill(u16 trainerId)