mirror of
https://github.com/rh-hideout/pokeemerald-expansion.git
synced 2026-03-21 18:04:50 -05:00
Add AI contextual damage roll configs (#9568)
This commit is contained in:
parent
48bf615a67
commit
d12a6a46e5
|
|
@ -201,6 +201,7 @@ struct SimulatedDamage
|
|||
u16 minimum;
|
||||
u16 median;
|
||||
u16 maximum;
|
||||
u16 random;
|
||||
};
|
||||
|
||||
// Ai Data used when deciding which move to use, computed only once before each turn's start.
|
||||
|
|
|
|||
|
|
@ -7,19 +7,24 @@
|
|||
// Roll boundaries used by AI when scoring. Doesn't affect actual damage dealt.
|
||||
#define MAX_ROLL_PERCENTAGE DMG_ROLL_PERCENT_HI
|
||||
#define MIN_ROLL_PERCENTAGE DMG_ROLL_PERCENT_LO
|
||||
#define DMG_ROLL_PERCENTAGE ((MAX_ROLL_PERCENTAGE + MIN_ROLL_PERCENTAGE + 1) / 2) // Controls the damage roll the AI sees for the default roll. By default the 9th roll is seen
|
||||
#define DMG_ROLL_PERCENTAGE ((MAX_ROLL_PERCENTAGE + MIN_ROLL_PERCENTAGE + 1) / 2) // Controls the damage roll the AI sees for the median roll. By default the 9th roll is seen
|
||||
|
||||
enum DamageRollType
|
||||
{
|
||||
DMG_ROLL_LOWEST,
|
||||
DMG_ROLL_DEFAULT,
|
||||
DMG_ROLL_MEDIAN,
|
||||
DMG_ROLL_HIGHEST,
|
||||
DMG_ROLL_RANDOM,
|
||||
};
|
||||
|
||||
enum DamageCalcContext
|
||||
{
|
||||
AI_DEFENDING,
|
||||
AI_ATTACKING,
|
||||
AI_SWITCHIN_DEFENDING,
|
||||
AI_SWITCHIN_ATTACKING,
|
||||
AI_SHOULD_SETUP_DEFENDING,
|
||||
AI_ATTACKING_PARTNER,
|
||||
};
|
||||
|
||||
enum AiConsiderEndure
|
||||
|
|
@ -104,7 +109,7 @@ bool32 AI_CanBattlerEscape(enum BattlerId battler);
|
|||
bool32 IsBattlerTrapped(enum BattlerId battlerAtk, enum BattlerId battlerDef);
|
||||
s32 AI_WhoStrikesFirst(enum BattlerId battlerAI, enum BattlerId battler, enum Move aiMoveConsidered, enum Move playerMoveConsidered, enum ConsiderPriority considerPriority);
|
||||
bool32 CanTargetFaintAi(enum BattlerId battlerDef, enum BattlerId battlerAtk);
|
||||
u32 NoOfHitsForTargetToFaintBattler(enum BattlerId battlerDef, enum BattlerId battlerAtk, enum AiConsiderEndure considerEndure);
|
||||
u32 NoOfHitsForTargetToFaintBattler(enum BattlerId battlerDef, enum BattlerId battlerAtk, enum DamageCalcContext calcContext, enum AiConsiderEndure considerEndure);
|
||||
void GetBestDmgMovesFromBattler(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum DamageCalcContext calcContext, enum Move *bestMoves);
|
||||
u32 GetMoveIndex(enum BattlerId battler, enum Move move);
|
||||
bool32 IsBestDmgMove(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum DamageCalcContext calcContext, enum Move move);
|
||||
|
|
|
|||
|
|
@ -84,6 +84,21 @@
|
|||
#define AI_DAMAGES_THROUGH_BERRIES TRUE // AI will see through resist berries when considering a certain KO threshold for the purposes damage calcs; this is considered when comparing best moves to KO to still pick the actual OHKO if needed
|
||||
#define AI_IGNORE_BERRY_KO_THRESHOLD 2 // KO threshold AI must meet in order to treat it berry though it doesn't exist (ie. 2 means "If the AI can 2HKO with berry resisted attack + not-berry resisted next attack, ignore berry resistence when calcing first attack"). Requires AI_DAMAGES_THROUGH_BERRIES
|
||||
|
||||
// AI damage calc roll considerations
|
||||
#define AI_ROLL_MIN 1
|
||||
#define AI_ROLL_MEDIAN 2
|
||||
#define AI_ROLL_MAX 3
|
||||
#define AI_ROLL_RANDOM 4
|
||||
#define AI_ROLL_TYPE_COUNT 5
|
||||
|
||||
// Define which roll type to use in each context; overridden by AI_FLAG_RISKY and AI_FLAG_CONSERVATIVE
|
||||
#define AI_ROLL_ATTACKING AI_ROLL_MAX
|
||||
#define AI_ROLL_DEFENDING AI_ROLL_MEDIAN
|
||||
#define AI_ROLL_SWITCHIN_ATTACKING AI_ROLL_MEDIAN
|
||||
#define AI_ROLL_SWITCHIN_DEFENDING AI_ROLL_MEDIAN
|
||||
#define AI_ROLL_SHOULD_SETUP_DEFENDING AI_ROLL_MAX
|
||||
#define AI_ROLL_ATTACKING_PARTNER AI_ROLL_MAX
|
||||
|
||||
// AI prediction chances
|
||||
#define PREDICT_SWITCH_CHANCE 50
|
||||
#define PREDICT_MOVE_CHANCE 100
|
||||
|
|
|
|||
|
|
@ -233,6 +233,7 @@
|
|||
F(POKERUS_WEAK_VARIANT, pokerusWeakVariant, (u32, TRUE)) \
|
||||
|
||||
#define AI_CONFIG_DEFINITIONS(F) \
|
||||
F(AI_ROLL_ATTACKING, aiRollAttacking, (u32, AI_ROLL_TYPE_COUNT - 1)) \
|
||||
|
||||
#define GET_CONFIG_MAXIMUM(_typeMaxValue, ...) INVOKE_WITH_B(GET_CONFIG_MAXIMUM_, _typeMaxValue)
|
||||
#define GET_CONFIG_MAXIMUM_(_type, ...) FIRST(__VA_OPT__(FIRST(__VA_ARGS__),) MAX_BITS((sizeof(_type) * 8)))
|
||||
|
|
|
|||
|
|
@ -244,6 +244,7 @@ enum RandomTag
|
|||
RNG_FISHING_GEN3_STICKY,
|
||||
RNG_WILD_MON_TARGET,
|
||||
RNG_AI_FAKE_OUT_SAVE_ALLY,
|
||||
RNG_AI_DMG_ROLL_RANDOM,
|
||||
};
|
||||
|
||||
#define RandomWeighted(tag, ...) \
|
||||
|
|
|
|||
|
|
@ -3203,8 +3203,8 @@ static s32 AI_DoubleBattle(enum BattlerId battlerAtk, enum BattlerId battlerDef,
|
|||
bool32 hasTwoOpponents = HasTwoOpponents(battlerAtk);
|
||||
bool32 hasPartner = HasPartner(battlerAtk);
|
||||
u32 friendlyFireThreshold = GetFriendlyFireKOThreshold(battlerAtk);
|
||||
u32 noOfHitsToKOPartner = GetNoOfHitsToKOBattler(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING, CONSIDER_ENDURE);
|
||||
bool32 wouldPartnerFaint = hasPartner && CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING) && !partnerProtecting;
|
||||
u32 noOfHitsToKOPartner = GetNoOfHitsToKOBattler(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING_PARTNER, CONSIDER_ENDURE);
|
||||
bool32 wouldPartnerFaint = hasPartner && CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING_PARTNER) && !partnerProtecting;
|
||||
bool32 isFriendlyFireOK = !wouldPartnerFaint && (noOfHitsToKOPartner == 0 || noOfHitsToKOPartner > friendlyFireThreshold);
|
||||
|
||||
// check what effect partner is using
|
||||
|
|
@ -4104,7 +4104,7 @@ static enum MoveComparisonResult CompareMoveTwoTurnEffect(enum BattlerId battler
|
|||
static inline bool32 ShouldUseSpreadDamageMove(enum BattlerId battlerAtk, enum Move move, u32 moveIndex, u32 hitsToFaintOpposingBattler)
|
||||
{
|
||||
enum BattlerId partnerBattler = BATTLE_PARTNER(battlerAtk);
|
||||
u32 noOfHitsToFaintPartner = GetNoOfHitsToKOBattler(battlerAtk, partnerBattler, moveIndex, AI_ATTACKING, CONSIDER_ENDURE);
|
||||
u32 noOfHitsToFaintPartner = GetNoOfHitsToKOBattler(battlerAtk, partnerBattler, moveIndex, AI_ATTACKING_PARTNER, CONSIDER_ENDURE);
|
||||
u32 friendlyFireThreshold = GetFriendlyFireKOThreshold(battlerAtk);
|
||||
return (HasPartnerIgnoreFlags(battlerAtk)
|
||||
&& noOfHitsToFaintPartner != 0 // Immunity check
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ static bool32 ShouldSwitchIfHasBadOdds(enum BattlerId battler)
|
|||
hasSuperEffectiveMove = TRUE;
|
||||
|
||||
// Check if can win 1v1
|
||||
hitsToKOPlayer = GetNoOfHitsToKOBattler(battler, opposingBattler, moveIndex, AI_ATTACKING, CONSIDER_ENDURE);
|
||||
hitsToKOPlayer = GetNoOfHitsToKOBattler(battler, opposingBattler, moveIndex, AI_SWITCHIN_ATTACKING, CONSIDER_ENDURE);
|
||||
if (!canBattlerWin1v1 ) // Once we can win a 1v1 we don't need to track this, but want to run the rest of the function to keep the runtime the same regardless of when we find the winning move
|
||||
{
|
||||
isBattlerFirst = AI_IsFaster(battler, opposingBattler, aiMove, expectedMove, CONSIDER_PRIORITY);
|
||||
|
|
@ -625,7 +625,7 @@ static bool32 FindMonThatAbsorbsOpponentsMove(enum BattlerId battler)
|
|||
// Only check damage if it's a damaging move
|
||||
if (!IsBattleMoveStatus(aiMove))
|
||||
{
|
||||
if (!AI_DoesChoiceEffectBlockMove(battler, aiMove) && AI_GetDamage(battler, opposingBattler, moveIndex, AI_ATTACKING, gAiLogicData) > gBattleMons[opposingBattler].hp)
|
||||
if (!AI_DoesChoiceEffectBlockMove(battler, aiMove) && AI_GetDamage(battler, opposingBattler, moveIndex, AI_SWITCHIN_ATTACKING, gAiLogicData) > gBattleMons[opposingBattler].hp)
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
|
@ -2024,7 +2024,7 @@ static s32 GetMaxDamagePlayerCouldDealToSwitchin(enum BattlerId battler, enum Ba
|
|||
playerMove = SMART_SWITCHING_OMNISCIENT ? gBattleMons[opposingBattler].moves[moveIndex] : playerMoves[moveIndex];
|
||||
if (playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH && gBattleMons[opposingBattler].pp[moveIndex] > 0)
|
||||
{
|
||||
damageTaken = AI_GetDamage(opposingBattler, battler, moveIndex, AI_DEFENDING, gAiLogicData);
|
||||
damageTaken = AI_GetDamage(opposingBattler, battler, moveIndex, AI_SWITCHIN_DEFENDING, gAiLogicData);
|
||||
if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move
|
||||
{
|
||||
*bestPlayerMove = playerMove;
|
||||
|
|
@ -2055,7 +2055,7 @@ static s32 GetMaxPriorityDamagePlayerCouldDealToSwitchin(enum BattlerId battler,
|
|||
if (GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], playerMove) > 0
|
||||
&& playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH && gBattleMons[opposingBattler].pp[moveIndex] > 0)
|
||||
{
|
||||
damageTaken = AI_GetDamage(opposingBattler, battler, moveIndex, AI_DEFENDING, gAiLogicData);
|
||||
damageTaken = AI_GetDamage(opposingBattler, battler, moveIndex, AI_SWITCHIN_DEFENDING, gAiLogicData);
|
||||
if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move
|
||||
{
|
||||
*bestPlayerPriorityMove = playerMove;
|
||||
|
|
@ -2221,8 +2221,8 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
continue;
|
||||
|
||||
aiMove = gBattleMons[battler].moves[moveIndex];
|
||||
damageDealt = AI_GetDamage(battler, opposingBattler, moveIndex, AI_ATTACKING, gAiLogicData);
|
||||
hitsToKOPlayer = GetNoOfHitsToKOBattler(battler, opposingBattler, moveIndex, AI_ATTACKING, CONSIDER_ENDURE);
|
||||
damageDealt = AI_GetDamage(battler, opposingBattler, moveIndex, AI_SWITCHIN_ATTACKING, gAiLogicData);
|
||||
hitsToKOPlayer = GetNoOfHitsToKOBattler(battler, opposingBattler, moveIndex, AI_SWITCHIN_ATTACKING, CONSIDER_ENDURE);
|
||||
|
||||
// Offensive switchin decisions are based on which whether switchin moves first and whether it can win a 1v1
|
||||
isSwitchinFirst = AI_IsFaster(battler, opposingBattler, aiMove, bestPlayerMove, CONSIDER_PRIORITY);
|
||||
|
|
@ -2480,7 +2480,7 @@ static u32 GetBestMonVanilla(struct Pokemon *party, int firstId, int lastId, enu
|
|||
// Best damage
|
||||
if (aiMove != MOVE_NONE && !IsBattleMoveStatus(aiMove))
|
||||
{
|
||||
u32 aiDmg = AI_GetDamage(battler, opposingBattler, moveIndex, AI_ATTACKING, gAiLogicData);
|
||||
u32 aiDmg = AI_GetDamage(battler, opposingBattler, moveIndex, AI_SWITCHIN_ATTACKING, gAiLogicData);
|
||||
if (aiDmg > bestDamage)
|
||||
{
|
||||
bestDamage = aiDmg;
|
||||
|
|
@ -2625,7 +2625,7 @@ u32 AI_SelectRevivalBlessingMon(enum BattlerId battler)
|
|||
if (aiMove == MOVE_NONE || gBattleMons[battler].pp[moveIndex] == 0)
|
||||
continue;
|
||||
|
||||
s32 damage = AI_GetDamage(battler, opposingBattler, moveIndex, AI_ATTACKING, gAiLogicData);
|
||||
s32 damage = AI_GetDamage(battler, opposingBattler, moveIndex, AI_SWITCHIN_ATTACKING, gAiLogicData);
|
||||
if (damage > bestDamage)
|
||||
bestDamage = damage;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,23 @@ enum MoveTarget AI_GetBattlerMoveTargetType(enum BattlerId battler, enum Move mo
|
|||
return GetMoveTarget(move);
|
||||
}
|
||||
|
||||
u32 AI_GetDefaultDamageRollForContext(enum BattlerId battlerAtk, enum BattlerId battlerDef, u32 moveIndex, struct AiLogicData *aiData, u32 aiRoll)
|
||||
{
|
||||
switch (aiRoll)
|
||||
{
|
||||
case AI_ROLL_MIN:
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum;
|
||||
case AI_ROLL_MEDIAN:
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].median;
|
||||
case AI_ROLL_MAX:
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum;
|
||||
case AI_ROLL_RANDOM:
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].random;
|
||||
default:
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].median; // Default assumes it deals median damage
|
||||
}
|
||||
}
|
||||
|
||||
u32 AI_GetDamage(enum BattlerId battlerAtk, enum BattlerId battlerDef, u32 moveIndex, enum DamageCalcContext calcContext, struct AiLogicData *aiData)
|
||||
{
|
||||
if (calcContext == AI_ATTACKING && BattlerHasAi(battlerAtk))
|
||||
|
|
@ -99,7 +116,7 @@ u32 AI_GetDamage(enum BattlerId battlerAtk, enum BattlerId battlerDef, u32 moveI
|
|||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum;
|
||||
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)) // Conservative assumes it deals min damage
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum;
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].median; // Default assumes it deals median damage
|
||||
return AI_GetDefaultDamageRollForContext(battlerAtk, battlerDef, moveIndex, aiData, GetConfig(AI_ROLL_ATTACKING));
|
||||
}
|
||||
else if (calcContext == AI_DEFENDING && BattlerHasAi(battlerDef))
|
||||
{
|
||||
|
|
@ -107,7 +124,39 @@ u32 AI_GetDamage(enum BattlerId battlerAtk, enum BattlerId battlerDef, u32 moveI
|
|||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum;
|
||||
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)) // Conservative assumes it takes max damage
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum;
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].median; // Default assumes it takes median damage
|
||||
return AI_GetDefaultDamageRollForContext(battlerAtk, battlerDef, moveIndex, aiData, AI_ROLL_DEFENDING);
|
||||
}
|
||||
else if (calcContext == AI_SWITCHIN_ATTACKING && BattlerHasAi(battlerAtk))
|
||||
{
|
||||
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE)) // Risky assumes it deals max damage
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum;
|
||||
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)) // Conservative assumes it deals min damage
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum;
|
||||
return AI_GetDefaultDamageRollForContext(battlerAtk, battlerDef, moveIndex, aiData, AI_ROLL_SWITCHIN_ATTACKING);
|
||||
}
|
||||
else if (calcContext == AI_SWITCHIN_DEFENDING && BattlerHasAi(battlerDef))
|
||||
{
|
||||
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE)) // Risky assumes it takes min damage
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum;
|
||||
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)) // Conservative assumes it takes max damage
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum;
|
||||
return AI_GetDefaultDamageRollForContext(battlerAtk, battlerDef, moveIndex, aiData, AI_ROLL_SWITCHIN_DEFENDING);
|
||||
}
|
||||
else if (calcContext == AI_SHOULD_SETUP_DEFENDING && BattlerHasAi(battlerDef))
|
||||
{
|
||||
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE)) // Risky assumes it takes min damage
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum;
|
||||
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)) // Conservative assumes it takes max damage
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum;
|
||||
return AI_GetDefaultDamageRollForContext(battlerAtk, battlerDef, moveIndex, aiData, AI_ROLL_SHOULD_SETUP_DEFENDING);
|
||||
}
|
||||
else if (calcContext == AI_ATTACKING_PARTNER && BattlerHasAi(battlerAtk))
|
||||
{
|
||||
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE)) // Risky assumes it deals max damage
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum;
|
||||
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)) // Conservative assumes it deals min damage
|
||||
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum;
|
||||
return AI_GetDefaultDamageRollForContext(battlerAtk, battlerDef, moveIndex, aiData, AI_ROLL_ATTACKING_PARTNER);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -572,6 +621,14 @@ static inline s32 DmgRoll(s32 dmg)
|
|||
return dmg;
|
||||
}
|
||||
|
||||
static inline s32 RandomRollDmg(s32 dmg)
|
||||
{
|
||||
u32 randomRollPercentage = RandomUniform(RNG_AI_DMG_ROLL_RANDOM, MIN_ROLL_PERCENTAGE, MAX_ROLL_PERCENTAGE);
|
||||
dmg *= randomRollPercentage;
|
||||
dmg /= 100;
|
||||
return dmg;
|
||||
}
|
||||
|
||||
bool32 IsDamageMoveUnusable(struct BattleContext *ctx)
|
||||
{
|
||||
enum Ability battlerDefAbility;
|
||||
|
|
@ -682,6 +739,8 @@ static inline s32 GetDamageByRollType(s32 dmg, enum DamageRollType rollType)
|
|||
return LowestRollDmg(dmg);
|
||||
else if (rollType == DMG_ROLL_HIGHEST)
|
||||
return HighestRollDmg(dmg);
|
||||
else if (rollType == DMG_ROLL_RANDOM)
|
||||
return RandomRollDmg(dmg);
|
||||
else
|
||||
return DmgRoll(dmg);
|
||||
}
|
||||
|
|
@ -700,12 +759,13 @@ static inline void AI_RestoreBattlerTypes(enum BattlerId battlerAtk, enum Type *
|
|||
gBattleMons[battlerAtk].types[2] = types[2];
|
||||
}
|
||||
|
||||
static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianDamage, u16 *minimumDamage, u16 *maximumDamage)
|
||||
static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianDamage, u16 *minimumDamage, u16 *maximumDamage, u16 *randomDamage)
|
||||
{
|
||||
enum BattleMoveEffects effect = GetMoveEffect(ctx->move);
|
||||
u16 median = *medianDamage;
|
||||
u16 minimum = *minimumDamage;
|
||||
u16 maximum = *maximumDamage;
|
||||
u16 random = *randomDamage;
|
||||
|
||||
u32 strikeCount = GetMoveStrikeCount(ctx->move);
|
||||
|
||||
|
|
@ -719,7 +779,7 @@ static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianD
|
|||
median = 0;
|
||||
for (i = 0; i < partyCount; i++)
|
||||
median += CalculateMoveDamage(ctx);
|
||||
maximum = minimum = median;
|
||||
maximum = minimum = median = random;
|
||||
gBattleStruct->beatUpSlot = 0;
|
||||
}
|
||||
else if (strikeCount > 1 && effect != EFFECT_TRIPLE_KICK)
|
||||
|
|
@ -727,6 +787,7 @@ static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianD
|
|||
median *= strikeCount;
|
||||
minimum *= strikeCount;
|
||||
maximum *= strikeCount;
|
||||
random *= strikeCount;
|
||||
}
|
||||
else if (IsMultiHitMove(ctx->move))
|
||||
{
|
||||
|
|
@ -735,12 +796,14 @@ static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianD
|
|||
median *= GetMoveSpeciesPowerOverride_NumOfHits(ctx->move);
|
||||
minimum *= GetMoveSpeciesPowerOverride_NumOfHits(ctx->move);
|
||||
maximum *= GetMoveSpeciesPowerOverride_NumOfHits(ctx->move);
|
||||
random *= GetMoveSpeciesPowerOverride_NumOfHits(ctx->move);
|
||||
}
|
||||
else if (ctx->abilityAtk == ABILITY_SKILL_LINK)
|
||||
{
|
||||
median *= 5;
|
||||
minimum *= 5;
|
||||
maximum *= 5;
|
||||
random *= 5;
|
||||
}
|
||||
else if (ctx->holdEffectAtk == HOLD_EFFECT_LOADED_DICE)
|
||||
{
|
||||
|
|
@ -748,12 +811,14 @@ static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianD
|
|||
median /= 2;
|
||||
minimum *= 4;
|
||||
maximum *= 5;
|
||||
random *= RandomUniform(RNG_AI_DMG_ROLL_RANDOM, 4, 5);
|
||||
}
|
||||
else
|
||||
{
|
||||
median *= 3;
|
||||
minimum *= 2;
|
||||
maximum *= 5;
|
||||
random *= RandomUniform(RNG_AI_DMG_ROLL_RANDOM, 2, 5);
|
||||
}
|
||||
}
|
||||
else if (ctx->abilityAtk == ABILITY_PARENTAL_BOND
|
||||
|
|
@ -763,6 +828,7 @@ static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianD
|
|||
median += median / (B_PARENTAL_BOND_DMG >= GEN_7 ? 4 : 2);
|
||||
minimum += minimum / (B_PARENTAL_BOND_DMG >= GEN_7 ? 4 : 2);
|
||||
maximum += maximum / (B_PARENTAL_BOND_DMG >= GEN_7 ? 4 : 2);
|
||||
random += random / (B_PARENTAL_BOND_DMG >= GEN_7 ? 4 : 2);
|
||||
}
|
||||
|
||||
if (median == 0)
|
||||
|
|
@ -771,10 +837,13 @@ static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianD
|
|||
minimum = 1;
|
||||
if (maximum == 0)
|
||||
maximum = 1;
|
||||
if (random == 0)
|
||||
random = 1;
|
||||
|
||||
*medianDamage = median;
|
||||
*minimumDamage = minimum;
|
||||
*maximumDamage = maximum;
|
||||
*randomDamage = random;
|
||||
}
|
||||
|
||||
static inline bool32 ShouldCalcCritDamage(struct BattleContext *ctx)
|
||||
|
|
@ -903,7 +972,7 @@ struct SimulatedDamage AI_CalcDamage(enum Move move, enum BattlerId battlerAtk,
|
|||
s32 fixedDamage = DoFixedDamageMoveCalc(&ctx);
|
||||
if (fixedDamage != INT32_MAX)
|
||||
{
|
||||
simDamage.minimum = simDamage.median = simDamage.maximum = fixedDamage;
|
||||
simDamage.minimum = simDamage.median = simDamage.maximum = simDamage.random = fixedDamage;
|
||||
}
|
||||
else if (moveEffect == EFFECT_TRIPLE_KICK)
|
||||
{
|
||||
|
|
@ -916,11 +985,14 @@ struct SimulatedDamage AI_CalcDamage(enum Move move, enum BattlerId battlerAtk,
|
|||
damageByRollType = GetDamageByRollType(oneTripleKickHit, DMG_ROLL_LOWEST);
|
||||
simDamage.minimum += AI_ApplyModifiersAfterDmgRoll(&ctx, damageByRollType);
|
||||
|
||||
damageByRollType = GetDamageByRollType(oneTripleKickHit, DMG_ROLL_DEFAULT);
|
||||
damageByRollType = GetDamageByRollType(oneTripleKickHit, DMG_ROLL_MEDIAN);
|
||||
simDamage.median += AI_ApplyModifiersAfterDmgRoll(&ctx, damageByRollType);
|
||||
|
||||
damageByRollType = GetDamageByRollType(oneTripleKickHit, DMG_ROLL_HIGHEST);
|
||||
simDamage.maximum += AI_ApplyModifiersAfterDmgRoll(&ctx, damageByRollType);
|
||||
|
||||
damageByRollType = GetDamageByRollType(oneTripleKickHit, DMG_ROLL_RANDOM);
|
||||
simDamage.random += AI_ApplyModifiersAfterDmgRoll(&ctx, damageByRollType);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -930,15 +1002,18 @@ struct SimulatedDamage AI_CalcDamage(enum Move move, enum BattlerId battlerAtk,
|
|||
simDamage.minimum = GetDamageByRollType(damage, DMG_ROLL_LOWEST);
|
||||
simDamage.minimum = AI_ApplyModifiersAfterDmgRoll(&ctx, simDamage.minimum);
|
||||
|
||||
simDamage.median = GetDamageByRollType(damage, DMG_ROLL_DEFAULT);
|
||||
simDamage.median = GetDamageByRollType(damage, DMG_ROLL_MEDIAN);
|
||||
simDamage.median = AI_ApplyModifiersAfterDmgRoll(&ctx, simDamage.median);
|
||||
|
||||
simDamage.maximum = GetDamageByRollType(damage, DMG_ROLL_HIGHEST);
|
||||
simDamage.maximum = AI_ApplyModifiersAfterDmgRoll(&ctx, simDamage.maximum);
|
||||
|
||||
simDamage.random = GetDamageByRollType(damage, DMG_ROLL_RANDOM);
|
||||
simDamage.random = AI_ApplyModifiersAfterDmgRoll(&ctx, simDamage.random);
|
||||
}
|
||||
|
||||
if (GetActiveGimmick(battlerAtk) != GIMMICK_Z_MOVE)
|
||||
CalcDynamicMoveDamage(&ctx, &simDamage.median, &simDamage.minimum, &simDamage.maximum);
|
||||
CalcDynamicMoveDamage(&ctx, &simDamage.median, &simDamage.minimum, &simDamage.maximum, &simDamage.random);
|
||||
|
||||
AI_RestoreBattlerTypes(battlerAtk, types);
|
||||
}
|
||||
|
|
@ -947,6 +1022,7 @@ struct SimulatedDamage AI_CalcDamage(enum Move move, enum BattlerId battlerAtk,
|
|||
simDamage.minimum = 0;
|
||||
simDamage.median = 0;
|
||||
simDamage.maximum = 0;
|
||||
simDamage.random = 0;
|
||||
}
|
||||
|
||||
// convert multiper to AI_EFFECTIVENESS_xX
|
||||
|
|
@ -1492,14 +1568,14 @@ bool32 CanTargetFaintAi(enum BattlerId battlerDef, enum BattlerId battlerAtk)
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
u32 NoOfHitsForTargetToFaintBattler(enum BattlerId battlerDef, enum BattlerId battlerAtk, enum AiConsiderEndure considerEndure)
|
||||
u32 NoOfHitsForTargetToFaintBattler(enum BattlerId battlerDef, enum BattlerId battlerAtk, enum DamageCalcContext calcContext, enum AiConsiderEndure considerEndure)
|
||||
{
|
||||
u32 currNumberOfHits;
|
||||
u32 leastNumberOfHits = UNKNOWN_NO_OF_HITS;
|
||||
|
||||
for (u32 moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
|
||||
{
|
||||
currNumberOfHits = GetNoOfHitsToKOBattler(battlerDef, battlerAtk, moveIndex, AI_DEFENDING, considerEndure);
|
||||
currNumberOfHits = GetNoOfHitsToKOBattler(battlerDef, battlerAtk, moveIndex, calcContext, considerEndure);
|
||||
if (currNumberOfHits != 0)
|
||||
{
|
||||
if (currNumberOfHits < leastNumberOfHits)
|
||||
|
|
@ -1546,7 +1622,7 @@ void GetBestDmgMovesFromBattler(enum BattlerId battlerAtk, enum BattlerId battle
|
|||
{
|
||||
for (u32 moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
|
||||
{
|
||||
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, moveIndex, AI_ATTACKING))
|
||||
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, moveIndex, calcContext))
|
||||
bestMoves[countBestMoves++] = moves[moveIndex];
|
||||
}
|
||||
}
|
||||
|
|
@ -1592,7 +1668,7 @@ bool32 IsBestDmgMove(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum
|
|||
enum Move bestMoves[MAX_MON_MOVES] = {MOVE_NONE};
|
||||
u32 index = GetMoveIndex(battlerAtk, move);
|
||||
|
||||
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, index, AI_ATTACKING))
|
||||
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, index, calcContext))
|
||||
return TRUE;
|
||||
|
||||
GetBestDmgMovesFromBattler(battlerAtk, battlerDef, calcContext, bestMoves);
|
||||
|
|
@ -2463,7 +2539,7 @@ bool32 CanIndexMoveFaintTarget(enum BattlerId battlerAtk, enum BattlerId battler
|
|||
enum Move *moves = gBattleMons[battlerAtk].moves;
|
||||
|
||||
if (IsDoubleBattle() && battlerDef == BATTLE_PARTNER(battlerAtk))
|
||||
dmg = gAiLogicData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum; // Attacking partner, be careful
|
||||
dmg = AI_GetDamage(battlerAtk, battlerDef, moveIndex, AI_ATTACKING_PARTNER, gAiLogicData); // Attacking partner, be careful
|
||||
else
|
||||
dmg = AI_GetDamage(battlerAtk, battlerDef, moveIndex, calcContext, gAiLogicData);
|
||||
|
||||
|
|
@ -3948,7 +4024,7 @@ static inline bool32 RecoveryEnablesWinning1v1(enum BattlerId battlerAtk, enum B
|
|||
{
|
||||
if (!CanTargetFaintAi(battlerDef, battlerAtk)
|
||||
&& GetBestDmgFromBattler(battlerDef, battlerAtk, AI_DEFENDING) < healAmount
|
||||
&& NoOfHitsForTargetToFaintBattler(battlerDef, battlerAtk, CONSIDER_ENDURE) < NoOfHitsForTargetToFaintBattlerWithMod(battlerDef, battlerAtk, healAmount))
|
||||
&& NoOfHitsForTargetToFaintBattler(battlerDef, battlerAtk, AI_DEFENDING, CONSIDER_ENDURE) < NoOfHitsForTargetToFaintBattlerWithMod(battlerDef, battlerAtk, healAmount))
|
||||
return TRUE; // target can't faint attacker and is dealing less damage than we're healing
|
||||
else if (!CanTargetFaintAi(battlerDef, battlerAtk) && gAiLogicData->hpPercents[battlerAtk] < ENABLE_RECOVERY_THRESHOLD && RandomPercentage(RNG_AI_SHOULD_RECOVER, SHOULD_RECOVER_CHANCE))
|
||||
return TRUE; // target can't faint attacker at all, generally safe
|
||||
|
|
@ -4810,7 +4886,7 @@ static u32 GetStagesOfStatChange(enum StatChange statChange)
|
|||
static enum AIScore IncreaseStatUpScoreInternal(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum StatChange statChange, bool32 considerContrary)
|
||||
{
|
||||
enum AIScore tempScore = NO_INCREASE;
|
||||
u32 noOfHitsToFaint = NoOfHitsForTargetToFaintBattler(battlerDef, battlerAtk, DONT_CONSIDER_ENDURE);
|
||||
u32 noOfHitsToFaint = NoOfHitsForTargetToFaintBattler(battlerDef, battlerAtk, AI_SHOULD_SETUP_DEFENDING, DONT_CONSIDER_ENDURE);
|
||||
enum Move predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
|
||||
bool32 aiIsFaster = AI_IsFaster(battlerAtk, battlerDef, MOVE_NONE, predictedMoveSpeedCheck, DONT_CONSIDER_PRIORITY); // Don't care about the priority of our setup move, care about outspeeding otherwise
|
||||
bool32 shouldSetUp = ((noOfHitsToFaint >= 2 && aiIsFaster) || (noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == UNKNOWN_NO_OF_HITS);
|
||||
|
|
|
|||
|
|
@ -197,14 +197,14 @@ AI_SINGLE_BATTLE_TEST("AI prefers Earthquake over Drill Run if both require the
|
|||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI prefers a weaker move over a one with a downside effect if both require the same number of hits to ko")
|
||||
AI_SINGLE_BATTLE_TEST("AI prefers a weaker move over one with a downside effect if both require the same number of hits to ko")
|
||||
{
|
||||
enum Move move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE;
|
||||
enum Move expectedMove;
|
||||
u16 hp, turns;
|
||||
|
||||
// Both moves require the same number of turns but Flamethrower will be chosen over Overheat (powerful effect)
|
||||
PARAMETRIZE { move1 = MOVE_OVERHEAT; move2 = MOVE_FLAMETHROWER; hp = 300; expectedMove = MOVE_FLAMETHROWER; turns = 2; }
|
||||
PARAMETRIZE { move1 = MOVE_OVERHEAT; move2 = MOVE_FLAMETHROWER; hp = 320; expectedMove = MOVE_FLAMETHROWER; turns = 2; }
|
||||
// Overheat kill in least amount of turns
|
||||
PARAMETRIZE { move1 = MOVE_OVERHEAT; move2 = MOVE_FLAMETHROWER; hp = 250; expectedMove = MOVE_OVERHEAT; turns = 1; }
|
||||
|
||||
|
|
@ -1007,7 +1007,7 @@ AI_SINGLE_BATTLE_TEST("AI has a chance to prioritize last chance priority damage
|
|||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
|
||||
PLAYER(SPECIES_CAMERUPT) { Speed(2); Moves(MOVE_FLAMETHROWER, MOVE_CELEBRATE); }
|
||||
OPPONENT(SPECIES_FLOATZEL) { Level(90); Speed(1); HP(1); Moves(MOVE_WAVE_CRASH, MOVE_AQUA_JET); }
|
||||
OPPONENT(SPECIES_FLOATZEL) { Level(85); Speed(1); HP(1); Moves(MOVE_WAVE_CRASH, MOVE_AQUA_JET); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_AQUA_JET); }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -708,16 +708,33 @@ AI_DOUBLE_BATTLE_TEST("AI sees corresponding absorbing abilities on partners")
|
|||
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_DISCHARGE) == TARGET_FOES_AND_ALLY);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_ZIGZAGOON);
|
||||
PLAYER(SPECIES_ZIGZAGOON);
|
||||
OPPONENT(SPECIES_SLAKING) { Moves(move, MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_SLAKING) { Moves(move, MOVE_CONSTRICT); }
|
||||
OPPONENT(species) { HP(1); Ability(ability); Moves(MOVE_POUND, MOVE_EMBER, MOVE_ROUND); }
|
||||
} WHEN {
|
||||
if (ability != ABILITY_CLOUD_NINE)
|
||||
TURN { EXPECT_MOVE(opponentLeft, move); }
|
||||
else
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_SCRATCH); }
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_CONSTRICT); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI sees random rolls correctly")
|
||||
{
|
||||
PASSES_RANDOMLY(3, 15, RNG_AI_DMG_ROLL_RANDOM); // Slaking KOs with 3 rolls
|
||||
GIVEN {
|
||||
WITH_CONFIG(AI_ROLL_ATTACKING, AI_ROLL_RANDOM);
|
||||
ASSUME(GetMoveTarget(MOVE_DISCHARGE) == TARGET_FOES_AND_ALLY);
|
||||
ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_ZIGZAGOON);
|
||||
PLAYER(SPECIES_ZIGZAGOON);
|
||||
OPPONENT(SPECIES_SLAKING) { Moves(MOVE_DISCHARGE, MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_PIKACHU) { HP(1); Ability(ABILITY_LIGHTNING_ROD); Moves(MOVE_SCRATCH); }
|
||||
} WHEN {
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_SCRATCH); }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user