mirror of
https://github.com/rh-hideout/pokeemerald-expansion.git
synced 2026-04-26 02:14:22 -05:00
Improve AI's priority handling (#7337)
This commit is contained in:
parent
cf4bb3e286
commit
57fda0d060
|
|
@ -45,6 +45,13 @@ enum AIScore
|
|||
WORST_EFFECT = -10
|
||||
};
|
||||
|
||||
enum MoveComparisonResult
|
||||
{
|
||||
MOVE_NEUTRAL_COMPARISON,
|
||||
MOVE_WON_COMPARISON,
|
||||
MOVE_LOST_COMPARISON,
|
||||
};
|
||||
|
||||
// AI_TryToFaint
|
||||
#define FAST_KILL 6 // AI is faster and faints target
|
||||
#define SLOW_KILL 4 // AI is slower and faints target
|
||||
|
|
|
|||
|
|
@ -29,8 +29,10 @@ enum DamageCalcContext
|
|||
enum AiCompareMovesPriority
|
||||
{
|
||||
PRIORITY_EFFECT,
|
||||
PRIORITY_GUARANTEE,
|
||||
PRIORITY_ACCURACY,
|
||||
PRIORITY_NOT_CHARGING
|
||||
PRIORITY_NOT_CHARGING,
|
||||
PRIORITY_SPEED,
|
||||
};
|
||||
|
||||
enum AIPivot
|
||||
|
|
@ -54,6 +56,12 @@ enum AIConsiderGimmick
|
|||
USE_GIMMICK,
|
||||
};
|
||||
|
||||
enum ConsiderPriority
|
||||
{
|
||||
DONT_CONSIDER_PRIORITY,
|
||||
CONSIDER_PRIORITY,
|
||||
};
|
||||
|
||||
static inline bool32 IsMoveUnusable(u32 moveIndex, u32 move, u32 moveLimitations)
|
||||
{
|
||||
return move == MOVE_NONE
|
||||
|
|
@ -63,8 +71,10 @@ static inline bool32 IsMoveUnusable(u32 moveIndex, u32 move, u32 moveLimitations
|
|||
|
||||
typedef bool32 (*MoveFlag)(u32 move);
|
||||
|
||||
bool32 AI_IsFaster(u32 battlerAi, u32 battlerDef, u32 move);
|
||||
bool32 AI_IsSlower(u32 battlerAi, u32 battlerDef, u32 move);
|
||||
bool32 AI_IsFaster(u32 battlerAi, u32 battlerDef, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority);
|
||||
bool32 AI_IsSlower(u32 battlerAi, u32 battlerDef, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority);
|
||||
bool32 AI_IsPartyMonFaster(u32 battlerAi, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority);
|
||||
bool32 AI_IsPartyMonSlower(u32 battlerAi, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority);
|
||||
bool32 AI_RandLessThan(u32 val);
|
||||
u32 AI_GetDamage(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum DamageCalcContext calcContext, struct AiLogicData *aiData);
|
||||
bool32 IsAiVsAiBattle(void);
|
||||
|
|
@ -92,7 +102,7 @@ bool32 AI_BattlerAtMaxHp(u32 battler);
|
|||
u32 GetHealthPercentage(u32 battler);
|
||||
bool32 AI_CanBattlerEscape(u32 battler);
|
||||
bool32 IsBattlerTrapped(u32 battlerAtk, u32 battlerDef);
|
||||
s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 moveConsidered);
|
||||
s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 aiMoveConsidered, u32 playerMoveConsidered, enum ConsiderPriority considerPriority);
|
||||
bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk);
|
||||
u32 NoOfHitsForTargetToFaintBattler(u32 battlerDef, u32 battlerAtk);
|
||||
u32 GetBestDmgMoveFromBattler(u32 battlerAtk, u32 battlerDef, enum DamageCalcContext calcContext);
|
||||
|
|
@ -107,7 +117,6 @@ u32 AI_GetSwitchinWeather(struct BattlePokemon battleMon);
|
|||
enum WeatherState IsWeatherActive(u32 flags);
|
||||
bool32 CanAIFaintTarget(u32 battlerAtk, u32 battlerDef, u32 numHits);
|
||||
bool32 CanIndexMoveFaintTarget(u32 battlerAtk, u32 battlerDef, u32 index, enum DamageCalcContext calcContext);
|
||||
bool32 CanIndexMoveGuaranteeFaintTarget(u32 battlerAtk, u32 battlerDef, u32 index);
|
||||
bool32 HasDamagingMove(u32 battlerId);
|
||||
bool32 HasDamagingMoveOfType(u32 battlerId, u32 type);
|
||||
u32 GetBattlerSecondaryDamage(u32 battlerId);
|
||||
|
|
@ -131,6 +140,7 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove);
|
|||
void SetAIUsingGimmick(u32 battler, enum AIConsiderGimmick use);
|
||||
bool32 IsAIUsingGimmick(u32 battler);
|
||||
void DecideTerastal(u32 battler);
|
||||
bool32 CanEndureHit(u32 battler, u32 battlerTarget, u32 move);
|
||||
|
||||
// stat stage checks
|
||||
bool32 AnyStatIsRaised(u32 battlerId);
|
||||
|
|
@ -143,7 +153,7 @@ u32 CountNegativeStatStages(u32 battlerId);
|
|||
// move checks
|
||||
bool32 IsAffectedByPowder(u32 battler, u32 ability, enum ItemHoldEffect holdEffect);
|
||||
bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, enum DamageCategory category);
|
||||
s32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo);
|
||||
enum MoveComparisonResult AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo);
|
||||
struct SimulatedDamage AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, enum AIConsiderGimmick considerGimmickAtk, enum AIConsiderGimmick considerGimmickDef);
|
||||
struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, enum AIConsiderGimmick considerGimmickAtk, enum AIConsiderGimmick considerGimmickDef, u32 weather);
|
||||
bool32 AI_IsDamagedByRecoil(u32 battler);
|
||||
|
|
@ -269,7 +279,7 @@ void IncreaseConfusionScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score
|
|||
void IncreaseFrostbiteScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score);
|
||||
|
||||
s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, enum DamageCalcContext calcContext);
|
||||
u32 AI_WhoStrikesFirstPartyMon(u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 moveConsidered);
|
||||
u32 AI_WhoStrikesFirstPartyMon(u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 aiMoveConsidered, u32 playerMoveConsidered, enum ConsiderPriority ConsiderPriority);
|
||||
s32 AI_TryToClearStats(u32 battlerAtk, u32 battlerDef, bool32 isDoubleBattle);
|
||||
bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef);
|
||||
bool32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData);
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
#define ENABLE_RECOVERY_THRESHOLD 60 // HP percentage beneath which SHOULD_RECOVER_CHANCE is active
|
||||
#define SUCKER_PUNCH_CHANCE 50 // Chance for the AI to not use Sucker Punch if the player has a status move
|
||||
#define SUCKER_PUNCH_PREDICTION_CHANCE 50 // Additional chance for the AI to not use Sucker Punch if actively predicting a status move if SUCKER_PUNCH_CHANCE fails
|
||||
#define PRIORITIZE_LAST_CHANCE_CHANCE 50 // Chance the AI will prioritize Last Chance (priority move in the face of being outsped and KO'd) over Slow KO
|
||||
|
||||
// AI damage calc considerations
|
||||
#define RISKY_AI_CRIT_STAGE_THRESHOLD 2 // Stat stages at which Risky will assume it gets a crit
|
||||
|
|
|
|||
|
|
@ -202,6 +202,7 @@ enum RandomTag
|
|||
RNG_AI_STATUS_FOCUS_PUNCH,
|
||||
RNG_AI_BOOST_INTO_HAZE,
|
||||
RNG_AI_SHOULD_RECOVER,
|
||||
RNG_AI_PRIORITIZE_LAST_CHANCE,
|
||||
RNG_HEALER,
|
||||
RNG_DEXNAV_ENCOUNTER_LEVEL,
|
||||
RNG_AI_ASSUME_STATUS_SLEEP,
|
||||
|
|
|
|||
|
|
@ -1083,7 +1083,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
if (IsPowderMove(move) && !IsAffectedByPowder(battlerDef, aiData->abilities[battlerDef], aiData->holdEffects[battlerDef]))
|
||||
RETURN_SCORE_MINUS(10);
|
||||
|
||||
if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_IsFaster(battlerAtk, battlerDef, move))
|
||||
if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
|
||||
RETURN_SCORE_MINUS(10);
|
||||
|
||||
if (IsTwoTurnNotSemiInvulnerableMove(battlerAtk, move) && CanTargetFaintAi(battlerDef, battlerAtk))
|
||||
|
|
@ -1309,7 +1309,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
switch (moveEffect)
|
||||
{
|
||||
case EFFECT_HIT: // only applies to Vital Throw
|
||||
if (GetBattleMovePriority(battlerAtk, aiData->abilities[battlerAtk], move) < 0 && AI_IsFaster(battlerAtk, battlerDef, move) && aiData->hpPercents[battlerAtk] < 40)
|
||||
if (GetBattleMovePriority(battlerAtk, aiData->abilities[battlerAtk], move) < 0 && AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) && aiData->hpPercents[battlerAtk] < 40)
|
||||
ADJUST_SCORE(-2); // don't want to move last
|
||||
break;
|
||||
default:
|
||||
|
|
@ -1759,7 +1759,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
&& (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)
|
||||
&& !PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))
|
||||
{
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move)) // Attacker should go first
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Attacker should go first
|
||||
{
|
||||
if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF)
|
||||
ADJUST_SCORE(-10); // no anticipated move to disable
|
||||
|
|
@ -1781,7 +1781,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
&& (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)
|
||||
&& !DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
|
||||
{
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move)) // Attacker should go first
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Attacker should go first
|
||||
{
|
||||
if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF)
|
||||
ADJUST_SCORE(-10); // no anticipated move to encore
|
||||
|
|
@ -2163,7 +2163,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
break;
|
||||
case EFFECT_SPITE:
|
||||
case EFFECT_MIMIC:
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move)) // Attacker should go first
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Attacker should go first
|
||||
{
|
||||
if (gLastMoves[battlerDef] == MOVE_NONE
|
||||
|| gLastMoves[battlerDef] == 0xFFFF)
|
||||
|
|
@ -2320,7 +2320,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
if (isDoubleBattle)
|
||||
{
|
||||
if (IsHazardMove(aiData->partnerMove) // partner is going to set up hazards
|
||||
&& AI_IsFaster(BATTLE_PARTNER(battlerAtk), battlerAtk, aiData->partnerMove)) // partner is going to set up before the potential Defog
|
||||
&& AI_IsFaster(BATTLE_PARTNER(battlerAtk), battlerAtk, aiData->partnerMove, predictedMove, CONSIDER_PRIORITY)) // partner is going to set up before the potential Defog
|
||||
{
|
||||
ADJUST_SCORE(-10);
|
||||
break; // Don't use Defog if partner is going to set up hazards
|
||||
|
|
@ -2348,7 +2348,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
break;
|
||||
case EFFECT_SEMI_INVULNERABLE:
|
||||
if (predictedMove != MOVE_NONE
|
||||
&& AI_IsSlower(battlerAtk, battlerDef, move)
|
||||
&& AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)
|
||||
&& GetMoveEffect(predictedMove) == EFFECT_SEMI_INVULNERABLE)
|
||||
ADJUST_SCORE(-10); // Don't Fly/dig/etc if opponent is going to fly/dig/etc after you
|
||||
|
||||
|
|
@ -2495,7 +2495,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
case EFFECT_ME_FIRST:
|
||||
if (predictedMove != MOVE_NONE)
|
||||
{
|
||||
if (AI_IsSlower(battlerAtk, battlerDef, move))
|
||||
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
|
||||
ADJUST_SCORE(-10); // Target is predicted to go first, Me First will fail
|
||||
else if (GetMoveEffect(predictedMove) != GetMoveEffect(move))
|
||||
return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score);
|
||||
|
|
@ -2664,7 +2664,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
}
|
||||
break;
|
||||
case EFFECT_ELECTRIFY:
|
||||
if (AI_IsSlower(battlerAtk, battlerDef, move)
|
||||
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)
|
||||
//|| GetMoveTypeSpecial(battlerDef, predictedMove) == TYPE_ELECTRIC // Move will already be electric type
|
||||
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
|
||||
ADJUST_SCORE(-10);
|
||||
|
|
@ -2696,7 +2696,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
case EFFECT_INSTRUCT:
|
||||
{
|
||||
u32 instructedMove;
|
||||
if (AI_IsSlower(battlerAtk, battlerDef, move))
|
||||
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
|
||||
instructedMove = predictedMove;
|
||||
else
|
||||
instructedMove = gLastMoves[battlerDef];
|
||||
|
|
@ -2733,21 +2733,21 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
break;
|
||||
case EFFECT_QUASH:
|
||||
if (!isDoubleBattle
|
||||
|| AI_IsSlower(battlerAtk, battlerDef, move)
|
||||
|| AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)
|
||||
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
|
||||
ADJUST_SCORE(-10);
|
||||
break;
|
||||
case EFFECT_AFTER_YOU:
|
||||
if (!IsTargetingPartner(battlerAtk, battlerDef)
|
||||
|| !isDoubleBattle
|
||||
|| AI_IsSlower(battlerAtk, battlerDef, move)
|
||||
|| AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)
|
||||
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
|
||||
ADJUST_SCORE(-10);
|
||||
break;
|
||||
case EFFECT_SUCKER_PUNCH:
|
||||
if ((HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_STATUS) && RandomPercentage(RNG_AI_SUCKER_PUNCH, SUCKER_PUNCH_CHANCE)) // Player has a status move
|
||||
|| (IsBattleMoveStatus(predictedMove) && RandomPercentage(RNG_AI_SUCKER_PUNCH, SUCKER_PUNCH_PREDICTION_CHANCE) && (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_PREDICT_MOVE)) // AI actively predicting incoming status move
|
||||
|| AI_IsSlower(battlerAtk, battlerDef, move)) // Opponent going first
|
||||
|| AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Opponent going first
|
||||
ADJUST_SCORE(-10);
|
||||
break;
|
||||
case EFFECT_TAILWIND:
|
||||
|
|
@ -2782,7 +2782,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
ADJUST_SCORE(-10);
|
||||
break;
|
||||
case EFFECT_FLAIL:
|
||||
if (AI_IsSlower(battlerAtk, battlerDef, move) // Opponent should go first
|
||||
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) // Opponent should go first
|
||||
|| aiData->hpPercents[battlerAtk] > 50)
|
||||
ADJUST_SCORE(-4);
|
||||
break;
|
||||
|
|
@ -2817,7 +2817,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
else if (CanAIFaintTarget(battlerAtk, battlerDef, 0))
|
||||
ADJUST_SCORE(-10);
|
||||
else if (CanTargetFaintAi(battlerDef, battlerAtk)
|
||||
&& AI_IsSlower(battlerAtk, battlerDef, move))
|
||||
&& AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
|
||||
ADJUST_SCORE(-10);
|
||||
break;
|
||||
case EFFECT_JUNGLE_HEALING:
|
||||
|
|
@ -2848,7 +2848,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
u32 defPrio = GetBattleMovePriority(battlerDef, aiData->abilities[battlerDef], predictedMove);
|
||||
if (predictedMove == MOVE_NONE
|
||||
|| IsBattleMoveStatus(predictedMove)
|
||||
|| AI_IsSlower(battlerAtk, battlerDef, move)
|
||||
|| AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)
|
||||
|| defPrio < 1
|
||||
|| defPrio > 3) // Opponent going first or not using priority move
|
||||
ADJUST_SCORE(-10);
|
||||
|
|
@ -2924,9 +2924,7 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, AI_ATTACKING)
|
||||
&& effect != EFFECT_EXPLOSION && effect != EFFECT_MISTY_EXPLOSION)
|
||||
{
|
||||
if (CanIndexMoveGuaranteeFaintTarget(battlerAtk, battlerDef, movesetIndex))
|
||||
ADJUST_SCORE(1); // Bonus point if the KO is guaranteed
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move))
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY))
|
||||
ADJUST_SCORE(FAST_KILL);
|
||||
else
|
||||
ADJUST_SCORE(SLOW_KILL);
|
||||
|
|
@ -2935,7 +2933,10 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
&& GetWhichBattlerFasterOrTies(battlerAtk, battlerDef, TRUE) != AI_IS_FASTER
|
||||
&& GetBattleMovePriority(battlerAtk, gAiLogicData->abilities[battlerAtk], move) > 0)
|
||||
{
|
||||
ADJUST_SCORE(LAST_CHANCE);
|
||||
if (RandomPercentage(RNG_AI_PRIORITIZE_LAST_CHANCE, PRIORITIZE_LAST_CHANCE_CHANCE))
|
||||
ADJUST_SCORE(SLOW_KILL + 2); // Don't outscore Fast Kill (which gets a bonus point in AI_CompareDamagingMoves), but do outscore Slow Kill getting the same
|
||||
else
|
||||
ADJUST_SCORE(LAST_CHANCE);
|
||||
}
|
||||
|
||||
return score;
|
||||
|
|
@ -3009,7 +3010,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
// Adjust for always crit moves
|
||||
if (MoveAlwaysCrits(aiData->partnerMove) && aiData->abilities[battlerAtk] == ABILITY_ANGER_POINT)
|
||||
{
|
||||
if (AI_IsSlower(battlerAtk, battlerAtkPartner, move)) // Partner moving first
|
||||
if (AI_IsSlower(battlerAtk, battlerAtkPartner, move, predictedMove, CONSIDER_PRIORITY)) // Partner moving first
|
||||
{
|
||||
// discourage raising our attack since it's about to be maxed out
|
||||
if (IsAttackBoostMoveEffect(effect))
|
||||
|
|
@ -3182,7 +3183,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
case ABILITY_ANGER_POINT:
|
||||
if (MoveAlwaysCrits(move)
|
||||
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)
|
||||
&& AI_IsFaster(battlerAtk, battlerAtkPartner, move)
|
||||
&& AI_IsFaster(battlerAtk, battlerAtkPartner, move, predictedMove, CONSIDER_PRIORITY)
|
||||
&& isFriendlyFireOK)
|
||||
{
|
||||
if (MoveAlwaysCrits(move))
|
||||
|
|
@ -3501,7 +3502,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
case EFFECT_INSTRUCT:
|
||||
{
|
||||
u16 instructedMove;
|
||||
if (AI_IsFaster(battlerAtk, battlerAtkPartner, move))
|
||||
if (AI_IsFaster(battlerAtk, battlerAtkPartner, move, predictedMove, CONSIDER_PRIORITY))
|
||||
instructedMove = aiData->partnerMove;
|
||||
else
|
||||
instructedMove = gLastMoves[battlerAtkPartner];
|
||||
|
|
@ -3518,8 +3519,8 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
if (!(gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && HasMoveWithEffect(battlerAtkPartner, EFFECT_TRICK_ROOM))
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
|
||||
if (AI_IsSlower(battlerAtkPartner, FOE(battlerAtkPartner), aiData->partnerMove) // Opponent mon 1 goes before partner
|
||||
&& AI_IsSlower(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)), aiData->partnerMove)) // Opponent mon 2 goes before partner
|
||||
if (AI_IsSlower(battlerAtkPartner, FOE(battlerAtkPartner), aiData->partnerMove, predictedMove, CONSIDER_PRIORITY) // Opponent mon 1 goes before partner
|
||||
&& AI_IsSlower(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)), aiData->partnerMove, predictedMove, CONSIDER_PRIORITY)) // Opponent mon 2 goes before partner
|
||||
{
|
||||
if (partnerEffect == EFFECT_COUNTER || partnerEffect == EFFECT_MIRROR_COAT)
|
||||
break; // These moves need to go last
|
||||
|
|
@ -3528,8 +3529,8 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
break;
|
||||
case EFFECT_HEAL_PULSE:
|
||||
case EFFECT_HIT_ENEMY_HEAL_ALLY:
|
||||
if (AI_IsFaster(battlerAtk, FOE(battlerAtk), move)
|
||||
&& AI_IsFaster(battlerAtk, BATTLE_PARTNER(FOE(battlerAtk)), move)
|
||||
if (AI_IsFaster(battlerAtk, FOE(battlerAtk), move, predictedMove, CONSIDER_PRIORITY)
|
||||
&& AI_IsFaster(battlerAtk, BATTLE_PARTNER(FOE(battlerAtk)), move, predictedMove, CONSIDER_PRIORITY)
|
||||
&& gBattleMons[battlerAtkPartner].hp < gBattleMons[battlerAtkPartner].maxHP / 2)
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
break;
|
||||
|
|
@ -3635,16 +3636,50 @@ static bool32 IsPinchBerryItemEffect(enum ItemHoldEffect holdEffect)
|
|||
}
|
||||
}
|
||||
|
||||
static s32 CompareMoveAccuracies(u32 battlerAtk, u32 battlerDef, u32 moveSlot1, u32 moveSlot2)
|
||||
static enum MoveComparisonResult CompareMoveAccuracies(u32 battlerAtk, u32 battlerDef, u32 moveSlot1, u32 moveSlot2)
|
||||
{
|
||||
u32 acc1 = gAiLogicData->moveAccuracy[battlerAtk][battlerDef][moveSlot1];
|
||||
u32 acc2 = gAiLogicData->moveAccuracy[battlerAtk][battlerDef][moveSlot2];
|
||||
|
||||
if (acc1 > acc2)
|
||||
return 1;
|
||||
return MOVE_WON_COMPARISON;
|
||||
else if (acc2 > acc1)
|
||||
return -1;
|
||||
return 0;
|
||||
return MOVE_LOST_COMPARISON;
|
||||
return MOVE_NEUTRAL_COMPARISON;
|
||||
}
|
||||
|
||||
static enum MoveComparisonResult CompareMoveSpeeds(u32 battlerAtk, u32 battlerDef, u16 move1, u16 move2)
|
||||
{
|
||||
u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData);
|
||||
u32 speed1 = AI_WhoStrikesFirst(battlerAtk, battlerDef, move1, predictedMove, CONSIDER_PRIORITY);
|
||||
u32 speed2 = AI_WhoStrikesFirst(battlerAtk, battlerDef, move2, predictedMove, CONSIDER_PRIORITY);
|
||||
|
||||
if (speed1 == AI_IS_FASTER && speed2 == AI_IS_SLOWER)
|
||||
return MOVE_WON_COMPARISON;
|
||||
if (speed2 == AI_IS_FASTER && speed1 == AI_IS_SLOWER)
|
||||
return MOVE_LOST_COMPARISON;
|
||||
return MOVE_NEUTRAL_COMPARISON;
|
||||
}
|
||||
|
||||
static enum MoveComparisonResult CompareGuaranteeFaintTarget(u32 battlerAtk, u32 battlerDef, u16 moveSlot1, u16 moveSlot2, u16 *moves)
|
||||
{
|
||||
s32 dmg1, dmg2;
|
||||
bool32 guarantee1, guarantee2;
|
||||
|
||||
if (!(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT))
|
||||
return 0;
|
||||
|
||||
// Explictly care about guaranteed KOs universally
|
||||
dmg1 = gAiLogicData->simulatedDmg[battlerAtk][battlerDef][moveSlot1].minimum;
|
||||
guarantee1 = (gBattleMons[battlerDef].hp <= dmg1 && !CanEndureHit(battlerAtk, battlerDef, moves[moveSlot1]));
|
||||
dmg2 = gAiLogicData->simulatedDmg[battlerAtk][battlerDef][moveSlot2].minimum;
|
||||
guarantee2 = (gBattleMons[battlerDef].hp <= dmg2 && !CanEndureHit(battlerAtk, battlerDef, moves[moveSlot2]));
|
||||
|
||||
if (guarantee1 && !guarantee2)
|
||||
return MOVE_WON_COMPARISON;
|
||||
if (guarantee2 && !guarantee1)
|
||||
return MOVE_LOST_COMPARISON;
|
||||
return MOVE_NEUTRAL_COMPARISON;
|
||||
}
|
||||
|
||||
static inline bool32 ShouldUseSpreadDamageMove(u32 battlerAtk, u32 move, u32 moveIndex, u32 hitsToFaintOpposingBattler)
|
||||
|
|
@ -3718,9 +3753,11 @@ static void AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef)
|
|||
|
||||
// Priority list:
|
||||
// 1. Less no of hits to ko
|
||||
// 2. Not charging
|
||||
// 3. More accuracy
|
||||
// 4. Better effect
|
||||
// 2. Priority if outsped and a OHKO
|
||||
// 3. Not charging
|
||||
// 4. More accuracy
|
||||
// 5. Guaranteed KO
|
||||
// 6. Better effect
|
||||
|
||||
// Current move requires the least hits to KO. Compare with other moves.
|
||||
if (leastHits == noOfHits[currId])
|
||||
|
|
@ -3738,23 +3775,55 @@ static void AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef)
|
|||
else if (!isTwoTurnNotSemiInvulnerableMove[i] && isTwoTurnNotSemiInvulnerableMove[currId])
|
||||
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_NOT_CHARGING);
|
||||
|
||||
// Comparing KOs
|
||||
if (noOfHits[currId] == 1)
|
||||
{
|
||||
// Use priority to get fast KO if outsped
|
||||
switch (CompareMoveSpeeds(battlerAtk, battlerDef, moves[currId], moves[i]))
|
||||
{
|
||||
case MOVE_WON_COMPARISON:
|
||||
tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_SPEED);
|
||||
break;
|
||||
case MOVE_LOST_COMPARISON:
|
||||
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_SPEED);
|
||||
break;
|
||||
case MOVE_NEUTRAL_COMPARISON:
|
||||
break;
|
||||
}
|
||||
// Min roll KOs
|
||||
switch (CompareGuaranteeFaintTarget(battlerAtk, battlerDef, currId, i, moves))
|
||||
{
|
||||
case MOVE_WON_COMPARISON:
|
||||
tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_GUARANTEE);
|
||||
break;
|
||||
case MOVE_LOST_COMPARISON:
|
||||
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_GUARANTEE);
|
||||
break;
|
||||
case MOVE_NEUTRAL_COMPARISON:
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (CompareMoveAccuracies(battlerAtk, battlerDef, currId, i))
|
||||
{
|
||||
case 1:
|
||||
case MOVE_WON_COMPARISON:
|
||||
tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_ACCURACY);
|
||||
break;
|
||||
case -1:
|
||||
case MOVE_LOST_COMPARISON:
|
||||
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_ACCURACY);
|
||||
break;
|
||||
case MOVE_NEUTRAL_COMPARISON:
|
||||
break;
|
||||
}
|
||||
switch (AI_WhichMoveBetter(moves[currId], moves[i], battlerAtk, battlerDef, noOfHits[currId]))
|
||||
{
|
||||
case 1:
|
||||
case MOVE_WON_COMPARISON:
|
||||
tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_EFFECT);
|
||||
break;
|
||||
case -1:
|
||||
case MOVE_LOST_COMPARISON:
|
||||
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_EFFECT);
|
||||
break;
|
||||
case MOVE_NEUTRAL_COMPARISON:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4111,7 +4180,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
|||
ADJUST_SCORE(IncreaseSubstituteMoveScore(battlerAtk, battlerDef, move));
|
||||
break;
|
||||
case EFFECT_MIMIC:
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move))
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
|
||||
{
|
||||
if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF
|
||||
&& (GetMoveEffect(gLastMoves[battlerDef]) != GetMoveEffect(move)))
|
||||
|
|
@ -4180,7 +4249,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
|||
&& (gLastMoves[battlerDef] != MOVE_NONE)
|
||||
&& (gLastMoves[battlerDef] != 0xFFFF)
|
||||
&& (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)
|
||||
&& (AI_IsFaster(battlerAtk, battlerDef, move)))
|
||||
&& (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)))
|
||||
{
|
||||
if (CanTargetMoveFaintAi(gLastMoves[battlerDef], battlerDef, battlerAtk, 1))
|
||||
ADJUST_SCORE(GOOD_EFFECT); // Disable move that can kill attacker
|
||||
|
|
@ -4220,7 +4289,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
|||
case EFFECT_DESTINY_BOND:
|
||||
if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
|
||||
break;
|
||||
else if (AI_IsFaster(battlerAtk, battlerDef, move) && CanTargetFaintAi(battlerDef, battlerAtk))
|
||||
else if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) && CanTargetFaintAi(battlerDef, battlerAtk))
|
||||
ADJUST_SCORE(GOOD_EFFECT);
|
||||
break;
|
||||
case EFFECT_SPITE:
|
||||
|
|
@ -4435,7 +4504,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
|||
if (predictedMove != MOVE_NONE && !isDoubleBattle)
|
||||
{
|
||||
enum BattleMoveEffects predictedEffect = GetMoveEffect(predictedMove);
|
||||
if ((AI_IsFaster(battlerAtk, battlerDef, move))
|
||||
if ((AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
|
||||
&& (predictedEffect == EFFECT_EXPLOSION
|
||||
|| predictedEffect == EFFECT_MISTY_EXPLOSION
|
||||
|| predictedEffect == EFFECT_PROTECT))
|
||||
|
|
@ -4486,7 +4555,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
|||
break;
|
||||
case EFFECT_ATTRACT:
|
||||
if (!isDoubleBattle
|
||||
&& (AI_IsSlower(battlerAtk, battlerDef, move))
|
||||
&& (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
|
||||
&& BattlerWillFaintFromSecondaryDamage(battlerDef, aiData->abilities[battlerDef]))
|
||||
break; // Don't use if the attract won't have a change to activate
|
||||
if (gBattleMons[battlerDef].status1 & STATUS1_ANY
|
||||
|
|
@ -4513,7 +4582,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
|||
if (isDoubleBattle)
|
||||
{
|
||||
if (IsHazardMove(aiData->partnerMove) // Partner is going to set up hazards
|
||||
&& AI_IsSlower(battlerAtk, BATTLE_PARTNER(battlerAtk), move)) // Partner going first
|
||||
&& AI_IsSlower(battlerAtk, BATTLE_PARTNER(battlerAtk), move, predictedMove, CONSIDER_PRIORITY)) // Partner going first
|
||||
break; // Don't use Defog if partner is going to set up hazards
|
||||
}
|
||||
ADJUST_SCORE(IncreaseStatDownScore(battlerAtk, battlerDef, STAT_EVASION));
|
||||
|
|
@ -5007,7 +5076,7 @@ case EFFECT_GUARD_SPLIT:
|
|||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
break;
|
||||
case EFFECT_HEAL_BLOCK:
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move) && predictedMove != MOVE_NONE && IsHealingMove(predictedMove))
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) && predictedMove != MOVE_NONE && IsHealingMove(predictedMove))
|
||||
ADJUST_SCORE(DECENT_EFFECT); // Try to cancel healing move
|
||||
else if (HasHealingEffect(battlerDef) || aiData->holdEffects[battlerDef] == HOLD_EFFECT_LEFTOVERS
|
||||
|| (aiData->holdEffects[battlerDef] == HOLD_EFFECT_BLACK_SLUDGE && IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON)))
|
||||
|
|
@ -5041,7 +5110,7 @@ case EFFECT_GUARD_SPLIT:
|
|||
ADJUST_SCORE(BEST_EFFECT);
|
||||
break;
|
||||
case EFFECT_QUASH:
|
||||
if (isDoubleBattle && AI_IsSlower(BATTLE_PARTNER(battlerAtk), battlerDef, aiData->partnerMove))
|
||||
if (isDoubleBattle && AI_IsSlower(BATTLE_PARTNER(battlerAtk), battlerDef, aiData->partnerMove, predictedMove, CONSIDER_PRIORITY))
|
||||
ADJUST_SCORE(DECENT_EFFECT); // Attacker partner wouldn't go before target
|
||||
break;
|
||||
case EFFECT_TAILWIND:
|
||||
|
|
@ -5056,7 +5125,7 @@ case EFFECT_GUARD_SPLIT:
|
|||
if (IsBattlerGrounded(battlerAtk) && HasDamagingMoveOfType(battlerDef, TYPE_ELECTRIC)
|
||||
&& !(effectiveness == UQ_4_12(0.0))) // Doesn't resist ground move
|
||||
{
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move)) // Attacker goes first
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Attacker goes first
|
||||
{
|
||||
if (predictedType == TYPE_GROUND)
|
||||
ADJUST_SCORE(GOOD_EFFECT); // Cause the enemy's move to fail
|
||||
|
|
@ -5071,7 +5140,7 @@ case EFFECT_GUARD_SPLIT:
|
|||
}
|
||||
break;
|
||||
case EFFECT_CAMOUFLAGE:
|
||||
if (predictedMove != MOVE_NONE && AI_IsFaster(battlerAtk, battlerDef, move) // Attacker goes first
|
||||
if (predictedMove != MOVE_NONE && AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) // Attacker goes first
|
||||
&& !IsBattleMoveStatus(move) && effectiveness != UQ_4_12(0.0))
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
break;
|
||||
|
|
@ -5098,7 +5167,7 @@ case EFFECT_GUARD_SPLIT:
|
|||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
break;
|
||||
case EFFECT_ENDEAVOR:
|
||||
if (AI_IsSlower(battlerAtk, battlerDef, move) && !CanTargetFaintAi(battlerDef, battlerAtk))
|
||||
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) && !CanTargetFaintAi(battlerDef, battlerAtk))
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
break;
|
||||
case EFFECT_REVIVAL_BLESSING:
|
||||
|
|
@ -5377,7 +5446,7 @@ case EFFECT_GUARD_SPLIT:
|
|||
case MOVE_EFFECT_THROAT_CHOP:
|
||||
if (IsSoundMove(GetBestDmgMoveFromBattler(battlerDef, battlerAtk, AI_DEFENDING)))
|
||||
{
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move))
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
|
||||
ADJUST_SCORE(GOOD_EFFECT);
|
||||
else
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
|
|
@ -5433,7 +5502,7 @@ static s32 AI_ForceSetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32
|
|||
return score;
|
||||
|
||||
if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_SMART_SWITCHING
|
||||
&& AI_IsSlower(battlerAtk, battlerDef, move)
|
||||
&& AI_IsSlower(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY)
|
||||
&& CanTargetFaintAi(battlerDef, battlerAtk)
|
||||
&& GetBattleMovePriority(battlerAtk, gAiLogicData->abilities[battlerAtk], move) == 0)
|
||||
{
|
||||
|
|
@ -6009,7 +6078,7 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
ADJUST_SCORE(GOOD_EFFECT);
|
||||
else if (hitsToKO == 1)
|
||||
ADJUST_SCORE(BEST_EFFECT);
|
||||
else if (IsSwitchOutEffect(GetMoveEffect(predictedMove)) && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Pursuit against fast U-Turn
|
||||
else if (IsSwitchOutEffect(GetMoveEffect(predictedMove)) && AI_WhoStrikesFirst(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) == AI_IS_SLOWER) // Pursuit against fast U-Turn
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,6 +171,34 @@ static bool32 AI_DoesChoiceEffectBlockMove(u32 battler, u32 move)
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
static inline bool32 CanBattlerWin1v1(u32 hitsToKOAI, u32 hitsToKOPlayer, bool32 isBattlerFirst)
|
||||
{
|
||||
// Player's best move deals 0 damage
|
||||
if (hitsToKOAI == 0 && hitsToKOPlayer > 0)
|
||||
return TRUE;
|
||||
|
||||
// AI's best move deals 0 damage
|
||||
if (hitsToKOPlayer == 0 && hitsToKOAI > 0)
|
||||
return FALSE;
|
||||
|
||||
// Neither mon can damage the other
|
||||
if (hitsToKOPlayer == 0 && hitsToKOAI == 0)
|
||||
return FALSE;
|
||||
|
||||
// Different KO thresholds depending on who goes first
|
||||
if (isBattlerFirst)
|
||||
{
|
||||
if (hitsToKOAI >= hitsToKOPlayer)
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hitsToKOAI > hitsToKOPlayer)
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Note that as many return statements as possible are INTENTIONALLY put after all of the loops;
|
||||
// the function can take a max of about 0.06s to run, and this prevents the player from identifying
|
||||
// whether the mon will switch or not by seeing how long the delay is before they select a move
|
||||
|
|
@ -178,12 +206,13 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler)
|
|||
{
|
||||
//Variable initialization
|
||||
u8 opposingPosition;
|
||||
s32 i, damageDealt = 0, maxDamageDealt = 0, damageTaken = 0, maxDamageTaken = 0;
|
||||
u32 aiMove, playerMove, aiBestMove = MOVE_NONE, aiAbility = gAiLogicData->abilities[battler], opposingBattler;
|
||||
s32 i, damageDealt = 0, maxDamageDealt = 0, damageTaken = 0, maxDamageTaken = 0, maxDamageTakenPriority = 0;
|
||||
u32 aiMove, playerMove, bestPlayerPriorityMove = MOVE_NONE, aiAbility = gAiLogicData->abilities[battler], opposingBattler;
|
||||
bool32 getsOneShot = FALSE, hasStatusMove = FALSE, hasSuperEffectiveMove = FALSE;
|
||||
u32 typeMatchup;
|
||||
enum BattleMoveEffects aiMoveEffect;
|
||||
u32 hitsToKoPlayer = 0, hitsToKoAI = 0;
|
||||
u32 hitsToKoAI = 0, hitsToKoAIPriority = 0, hitsToKoPlayer = 0;
|
||||
bool32 canBattlerWin1v1 = FALSE, isBattlerFirst, isBattlerFirstPriority;
|
||||
|
||||
// Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer
|
||||
if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING))
|
||||
|
|
@ -197,6 +226,28 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler)
|
|||
opposingBattler = GetBattlerAtPosition(opposingPosition);
|
||||
u16 *playerMoves = GetMovesArray(opposingBattler);
|
||||
|
||||
// Get max damage mon could take
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
playerMove = SMART_SWITCHING_OMNISCIENT ? gBattleMons[opposingBattler].moves[i] : playerMoves[i];
|
||||
if (playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH)
|
||||
{
|
||||
damageTaken = AI_GetDamage(opposingBattler, battler, i, AI_DEFENDING, gAiLogicData);
|
||||
if (damageTaken > maxDamageTaken && !AI_DoesChoiceEffectBlockMove(opposingBattler, playerMove))
|
||||
{
|
||||
maxDamageTaken = damageTaken;
|
||||
}
|
||||
if (GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[battler], playerMove) > 0 && damageTaken > maxDamageTakenPriority && !AI_DoesChoiceEffectBlockMove(opposingBattler, playerMove))
|
||||
{
|
||||
maxDamageTakenPriority = damageTaken;
|
||||
bestPlayerPriorityMove = playerMove;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hitsToKoAI = GetNoOfHitsToKOBattlerDmg(maxDamageTaken, battler);
|
||||
hitsToKoAIPriority = GetNoOfHitsToKOBattlerDmg(maxDamageTakenPriority, battler);
|
||||
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
aiMove = gBattleMons[battler].moves[i];
|
||||
|
|
@ -220,53 +271,37 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler)
|
|||
}
|
||||
|
||||
// Only check damage if it's a damaging move
|
||||
if (!IsBattleMoveStatus(aiMove))
|
||||
if (!IsBattleMoveStatus(aiMove) && !AI_DoesChoiceEffectBlockMove(battler, aiMove))
|
||||
{
|
||||
// Check if mon has a super effective move
|
||||
if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) >= UQ_4_12(2.0) && !AI_DoesChoiceEffectBlockMove(battler, aiMove))
|
||||
if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) >= UQ_4_12(2.0))
|
||||
hasSuperEffectiveMove = TRUE;
|
||||
|
||||
// Get maximum damage mon can deal
|
||||
damageDealt = AI_GetDamage(battler, opposingBattler, i, AI_ATTACKING, gAiLogicData);
|
||||
if (damageDealt > maxDamageDealt && !AI_DoesChoiceEffectBlockMove(battler, aiMove))
|
||||
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
|
||||
{
|
||||
maxDamageDealt = damageDealt;
|
||||
aiBestMove = aiMove;
|
||||
hitsToKoPlayer = GetNoOfHitsToKOBattlerDmg(damageDealt, opposingBattler);
|
||||
isBattlerFirst = AI_IsFaster(battler, opposingBattler, aiMove, GetIncomingMove(battler, opposingBattler, gAiLogicData), CONSIDER_PRIORITY);
|
||||
isBattlerFirstPriority = AI_IsFaster(battler, opposingBattler, aiMove, bestPlayerPriorityMove, CONSIDER_PRIORITY);
|
||||
canBattlerWin1v1 = CanBattlerWin1v1(hitsToKoAI, hitsToKoPlayer, isBattlerFirst) && CanBattlerWin1v1(hitsToKoAIPriority, hitsToKoPlayer, isBattlerFirstPriority);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hitsToKoPlayer = GetNoOfHitsToKOBattlerDmg(maxDamageDealt, opposingBattler);
|
||||
|
||||
// Calculate type advantage
|
||||
typeMatchup = GetBattleMonTypeMatchup(gBattleMons[opposingBattler], gBattleMons[battler]);
|
||||
|
||||
// Get max damage mon could take
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
playerMove = SMART_SWITCHING_OMNISCIENT ? gBattleMons[opposingBattler].moves[i] : playerMoves[i];
|
||||
if (playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH)
|
||||
{
|
||||
damageTaken = AI_GetDamage(opposingBattler, battler, i, AI_DEFENDING, gAiLogicData);
|
||||
if (damageTaken > maxDamageTaken && !AI_DoesChoiceEffectBlockMove(opposingBattler, playerMove))
|
||||
{
|
||||
maxDamageTaken = damageTaken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hitsToKoAI = GetNoOfHitsToKOBattlerDmg(maxDamageTaken, battler);
|
||||
|
||||
// Check if mon gets one shot
|
||||
if (maxDamageTaken > gBattleMons[battler].hp
|
||||
&& !(gItemsInfo[gBattleMons[battler].item].holdEffect == HOLD_EFFECT_FOCUS_SASH || (!IsMoldBreakerTypeAbility(opposingBattler, gBattleMons[opposingBattler].ability) && B_STURDY >= GEN_5 && aiAbility == ABILITY_STURDY)))
|
||||
&& !(gItemsInfo[gBattleMons[battler].item].holdEffect == HOLD_EFFECT_FOCUS_SASH || (!IsMoldBreakerTypeAbility(opposingBattler, gAiLogicData->abilities[opposingBattler]) && B_STURDY >= GEN_5 && aiAbility == ABILITY_STURDY)))
|
||||
{
|
||||
getsOneShot = TRUE;
|
||||
}
|
||||
|
||||
// Check if current mon can 1v1 in spite of bad matchup, and don't switch out if it can
|
||||
if ((hitsToKoPlayer != 0 && (hitsToKoPlayer < hitsToKoAI || hitsToKoAI == 0)) || (hitsToKoPlayer == hitsToKoAI && AI_IsFaster(battler, opposingBattler, aiBestMove)))
|
||||
if (canBattlerWin1v1)
|
||||
return FALSE;
|
||||
|
||||
// If we don't have any other viable options, don't switch out
|
||||
|
|
@ -384,8 +419,8 @@ static bool32 ShouldSwitchIfAllMovesBad(u32 battler)
|
|||
{
|
||||
aiMove = gBattleMons[battler].moves[moveIndex];
|
||||
if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) > UQ_4_12(0.0) && aiMove != MOVE_NONE
|
||||
&& !CanAbilityAbsorbMove(battler, opposingBattler, gBattleMons[opposingBattler].ability, aiMove, GetBattleMoveType(aiMove), AI_CHECK)
|
||||
&& !CanAbilityBlockMove(battler, opposingBattler, gBattleMons[battler].ability, gBattleMons[opposingBattler].ability, aiMove, AI_CHECK)
|
||||
&& !CanAbilityAbsorbMove(battler, opposingBattler, gAiLogicData->abilities[opposingBattler], aiMove, GetBattleMoveType(aiMove), AI_CHECK)
|
||||
&& !CanAbilityBlockMove(battler, opposingBattler, gBattleMons[battler].ability, gAiLogicData->abilities[opposingBattler], aiMove, AI_CHECK)
|
||||
&& (!ALL_MOVES_BAD_STATUS_MOVES_BAD || gMovesInfo[aiMove].power != 0)) // If using ALL_MOVES_BAD_STATUS_MOVES_BAD, then need power to be non-zero
|
||||
return FALSE;
|
||||
}
|
||||
|
|
@ -1788,7 +1823,7 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, u32 battler)
|
|||
u16 maxHP = gAiLogicData->switchinCandidate.battleMon.maxHP, item = gAiLogicData->switchinCandidate.battleMon.item, heldItemEffect = GetItemHoldEffect(item);
|
||||
u8 weatherDuration = gWishFutureKnock.weatherDuration, holdEffectParam = GetItemHoldEffectParam(item);
|
||||
u32 opposingBattler = GetOppositeBattler(battler);
|
||||
u32 opposingAbility = gBattleMons[opposingBattler].ability, ability = gAiLogicData->switchinCandidate.battleMon.ability;
|
||||
u32 opposingAbility = gAiLogicData->abilities[opposingBattler], ability = gAiLogicData->switchinCandidate.battleMon.ability;
|
||||
bool32 usedSingleUseHealingItem = FALSE, opponentCanBreakMold = IsMoldBreakerTypeAbility(opposingBattler, opposingAbility);
|
||||
s32 currentHP = startingHP;
|
||||
|
||||
|
|
@ -1939,7 +1974,7 @@ static int GetRandomSwitchinWithBatonPass(int aliveCount, int bits, int firstId,
|
|||
return PARTY_SIZE;
|
||||
}
|
||||
|
||||
static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattler, struct BattlePokemon battleMon)
|
||||
static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattler, struct BattlePokemon battleMon, u32 *bestPlayerMove)
|
||||
{
|
||||
int i = 0;
|
||||
u32 playerMove;
|
||||
|
|
@ -1955,7 +1990,35 @@ static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattle
|
|||
if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move
|
||||
return damageTaken;
|
||||
if (damageTaken > maxDamageTaken)
|
||||
{
|
||||
maxDamageTaken = damageTaken;
|
||||
*bestPlayerMove = playerMove;
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxDamageTaken;
|
||||
}
|
||||
|
||||
static s32 GetMaxPriorityDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattler, struct BattlePokemon battleMon, u32 *bestPlayerPriorityMove)
|
||||
{
|
||||
int i = 0;
|
||||
u32 playerMove;
|
||||
u16 *playerMoves = GetMovesArray(opposingBattler);
|
||||
s32 damageTaken = 0, maxDamageTaken = 0;
|
||||
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
playerMove = SMART_SWITCHING_OMNISCIENT ? gBattleMons[opposingBattler].moves[i] : playerMoves[i];
|
||||
if (GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], playerMove) > 0 && playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH)
|
||||
{
|
||||
damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, AI_DEFENDING);
|
||||
if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move
|
||||
return damageTaken;
|
||||
if (damageTaken > maxDamageTaken)
|
||||
{
|
||||
maxDamageTaken = damageTaken;
|
||||
*bestPlayerPriorityMove = playerMove;
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxDamageTaken;
|
||||
|
|
@ -2044,9 +2107,9 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
int i, j, aliveCount = 0, bits = 0, aceMonCount = 0;
|
||||
s32 defensiveMonHitKOThreshold = 3; // 3HKO threshold that candidate defensive mons must exceed
|
||||
s32 playerMonHP = gBattleMons[opposingBattler].hp, maxDamageDealt = 0, damageDealt = 0;
|
||||
u32 aiMove, hitsToKOAI, maxHitsToKO = 0;
|
||||
u32 aiMove, hitsToKOAI, hitsToKOPlayer, hitsToKOAIPriority, bestPlayerMove = MOVE_NONE, bestPlayerPriorityMove = MOVE_NONE, maxHitsToKO = 0;
|
||||
u32 bestResist = UQ_4_12(2.0), bestResistEffective = UQ_4_12(2.0), typeMatchup; // 2.0 is the default "Neutral" matchup from GetBattleMonTypeMatchup
|
||||
bool32 isFreeSwitch = IsFreeSwitch(switchType, battlerIn1, opposingBattler), isSwitchinFirst, canSwitchinWin1v1;
|
||||
bool32 isFreeSwitch = IsFreeSwitch(switchType, battlerIn1, opposingBattler), isSwitchinFirst, isSwitchinFirstPriority, canSwitchinWin1v1;
|
||||
|
||||
// Iterate through mons
|
||||
for (i = firstId; i < lastId; i++)
|
||||
|
|
@ -2077,7 +2140,8 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
continue;
|
||||
|
||||
// Get max number of hits for player to KO AI mon and type matchup for defensive switching
|
||||
hitsToKOAI = GetSwitchinHitsToKO(GetMaxDamagePlayerCouldDealToSwitchin(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon), battler);
|
||||
hitsToKOAI = GetSwitchinHitsToKO(GetMaxDamagePlayerCouldDealToSwitchin(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, &bestPlayerMove), battler);
|
||||
hitsToKOAIPriority = GetSwitchinHitsToKO(GetMaxPriorityDamagePlayerCouldDealToSwitchin(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, &bestPlayerPriorityMove), battler);
|
||||
typeMatchup = GetBattleMonTypeMatchup(gBattleMons[opposingBattler], gAiLogicData->switchinCandidate.battleMon);
|
||||
|
||||
// Check through current mon's moves
|
||||
|
|
@ -2085,10 +2149,12 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
{
|
||||
aiMove = gAiLogicData->switchinCandidate.battleMon.moves[j];
|
||||
damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, AI_ATTACKING);
|
||||
hitsToKOPlayer = GetNoOfHitsToKOBattlerDmg(damageDealt, opposingBattler);
|
||||
|
||||
// Offensive switchin decisions are based on which whether switchin moves first and whether it can win a 1v1
|
||||
isSwitchinFirst = AI_WhoStrikesFirstPartyMon(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, aiMove);
|
||||
canSwitchinWin1v1 = CanSwitchinWin1v1(hitsToKOAI, GetNoOfHitsToKOBattlerDmg(damageDealt, opposingBattler), isSwitchinFirst, isFreeSwitch);
|
||||
isSwitchinFirst = AI_IsPartyMonFaster(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, aiMove, bestPlayerMove, CONSIDER_PRIORITY);
|
||||
isSwitchinFirstPriority = AI_IsPartyMonFaster(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, aiMove, bestPlayerPriorityMove, CONSIDER_PRIORITY);
|
||||
canSwitchinWin1v1 = CanSwitchinWin1v1(hitsToKOAI, hitsToKOPlayer, isSwitchinFirst, isFreeSwitch) && CanSwitchinWin1v1(hitsToKOAIPriority, hitsToKOPlayer, isSwitchinFirstPriority, isFreeSwitch); // AI must successfully 1v1 with and without priority to be considered a good option
|
||||
|
||||
// Check for Baton Pass; hitsToKO requirements mean mon can boost and BP without dying whether it's slower or not
|
||||
if (GetMoveEffect(aiMove) == EFFECT_BATON_PASS)
|
||||
|
|
@ -2346,7 +2412,7 @@ static bool32 AiExpectsToFaintPlayer(u32 battler)
|
|||
|
||||
if (!IsBattlerAlly(target, battler)
|
||||
&& CanIndexMoveFaintTarget(battler, target, gAiBattleData->chosenMoveIndex[battler], AI_ATTACKING)
|
||||
&& AI_IsFaster(battler, target, GetAIChosenMove(battler)))
|
||||
&& AI_IsFaster(battler, target, GetAIChosenMove(battler), GetIncomingMove(battler, target, gAiLogicData), CONSIDER_PRIORITY))
|
||||
{
|
||||
// We expect to faint the target and move first -> dont use an item
|
||||
return TRUE;
|
||||
|
|
|
|||
|
|
@ -76,14 +76,24 @@ u32 AI_GetDamage(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum DamageCalcC
|
|||
}
|
||||
}
|
||||
|
||||
bool32 AI_IsFaster(u32 battlerAi, u32 battlerDef, u32 move)
|
||||
bool32 AI_IsFaster(u32 battlerAi, u32 battlerDef, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority)
|
||||
{
|
||||
return (AI_WhoStrikesFirst(battlerAi, battlerDef, move) == AI_IS_FASTER);
|
||||
return (AI_WhoStrikesFirst(battlerAi, battlerDef, aiMove, playerMove, considerPriority) == AI_IS_FASTER);
|
||||
}
|
||||
|
||||
bool32 AI_IsSlower(u32 battlerAi, u32 battlerDef, u32 move)
|
||||
bool32 AI_IsSlower(u32 battlerAi, u32 battlerDef, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority)
|
||||
{
|
||||
return (AI_WhoStrikesFirst(battlerAi, battlerDef, move) == AI_IS_SLOWER);
|
||||
return (AI_WhoStrikesFirst(battlerAi, battlerDef, aiMove, playerMove, considerPriority) == AI_IS_SLOWER);
|
||||
}
|
||||
|
||||
bool32 AI_IsPartyMonFaster(u32 battlerAi, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority)
|
||||
{
|
||||
return (AI_WhoStrikesFirstPartyMon(battlerAi, battlerDef, switchinCandidate, aiMove, playerMove, considerPriority) == AI_IS_FASTER);
|
||||
}
|
||||
|
||||
bool32 AI_IsPartyMonSlower(u32 battlerAi, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 aiMove, u32 playerMove, enum ConsiderPriority considerPriority)
|
||||
{
|
||||
return (AI_WhoStrikesFirstPartyMon(battlerAi, battlerDef, switchinCandidate, aiMove, playerMove, considerPriority) == AI_IS_SLOWER);
|
||||
}
|
||||
|
||||
u32 GetAIChosenMove(u32 battlerId)
|
||||
|
|
@ -484,7 +494,7 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler)
|
|||
enum BattleMoveEffects effect = GetMoveEffect(move);
|
||||
if (effect == EFFECT_PROTECT && move != MOVE_ENDURE)
|
||||
return TRUE;
|
||||
if (effect == EFFECT_SEMI_INVULNERABLE && AI_IsSlower(battlerAI, opposingBattler, GetAIChosenMove(battlerAI)))
|
||||
if (effect == EFFECT_SEMI_INVULNERABLE && AI_IsSlower(battlerAI, opposingBattler, GetAIChosenMove(battlerAI), GetIncomingMove(battlerAI, opposingBattler, gAiLogicData), CONSIDER_PRIORITY))
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
|
@ -1164,7 +1174,7 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s
|
|||
}
|
||||
|
||||
// Checks if one of the moves has side effects or perks, assuming equal dmg or equal no of hits to KO
|
||||
s32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo)
|
||||
enum MoveComparisonResult AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo)
|
||||
{
|
||||
bool32 effect1, effect2;
|
||||
u32 defAbility = gAiLogicData->abilities[battlerDef];
|
||||
|
|
@ -1178,27 +1188,27 @@ s32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32
|
|||
bool32 moveContact1 = MoveMakesContact(move1);
|
||||
bool32 moveContact2 = MoveMakesContact(move2);
|
||||
if (moveContact1 && !moveContact2)
|
||||
return -1;
|
||||
return MOVE_LOST_COMPARISON;
|
||||
if (moveContact2 && !moveContact1)
|
||||
return 1;
|
||||
return MOVE_WON_COMPARISON;
|
||||
}
|
||||
|
||||
// Check additional effects.
|
||||
effect1 = AI_IsMoveEffectInMinus(battlerAtk, battlerDef, move1, noOfHitsToKo);
|
||||
effect2 = AI_IsMoveEffectInMinus(battlerAtk, battlerDef, move2, noOfHitsToKo);
|
||||
if (effect2 && !effect1)
|
||||
return 1;
|
||||
return MOVE_WON_COMPARISON;
|
||||
if (effect1 && !effect2)
|
||||
return -1;
|
||||
return MOVE_LOST_COMPARISON;
|
||||
|
||||
effect1 = AI_IsMoveEffectInPlus(battlerAtk, battlerDef, move1, noOfHitsToKo);
|
||||
effect2 = AI_IsMoveEffectInPlus(battlerAtk, battlerDef, move2, noOfHitsToKo);
|
||||
if (effect2 && !effect1)
|
||||
return -1;
|
||||
return MOVE_LOST_COMPARISON;
|
||||
if (effect1 && !effect2)
|
||||
return 1;
|
||||
return MOVE_WON_COMPARISON;
|
||||
|
||||
return 0;
|
||||
return MOVE_NEUTRAL_COMPARISON;
|
||||
}
|
||||
|
||||
u32 GetNoOfHitsToKO(u32 dmg, s32 hp)
|
||||
|
|
@ -1261,7 +1271,7 @@ uq4_12_t AI_GetMoveEffectiveness(u32 move, u32 battlerAtk, u32 battlerDef)
|
|||
* AI_IS_FASTER: is user(ai) faster
|
||||
* AI_IS_SLOWER: is target faster
|
||||
*/
|
||||
s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 moveConsidered)
|
||||
s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 aiMoveConsidered, u32 playerMoveConsidered, enum ConsiderPriority considerPriority)
|
||||
{
|
||||
u32 speedBattlerAI, speedBattler;
|
||||
enum ItemHoldEffect holdEffectAI = gAiLogicData->holdEffects[battlerAI];
|
||||
|
|
@ -1269,15 +1279,16 @@ s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 moveConsidered)
|
|||
u32 abilityAI = gAiLogicData->abilities[battlerAI];
|
||||
u32 abilityPlayer = gAiLogicData->abilities[battler];
|
||||
|
||||
u32 predictedMove = GetIncomingMove(battlerAI, battler, gAiLogicData);
|
||||
if (considerPriority == CONSIDER_PRIORITY)
|
||||
{
|
||||
s8 aiPriority = GetBattleMovePriority(battlerAI, abilityAI, aiMoveConsidered);
|
||||
s8 playerPriority = GetBattleMovePriority(battler, abilityPlayer, playerMoveConsidered);
|
||||
|
||||
s8 aiPriority = GetBattleMovePriority(battlerAI, abilityAI, moveConsidered);
|
||||
s8 playerPriority = GetBattleMovePriority(battler, abilityPlayer, predictedMove);
|
||||
|
||||
if (aiPriority > playerPriority)
|
||||
return AI_IS_FASTER;
|
||||
else if (aiPriority < playerPriority)
|
||||
return AI_IS_SLOWER;
|
||||
if (aiPriority > playerPriority)
|
||||
return AI_IS_FASTER;
|
||||
else if (aiPriority < playerPriority)
|
||||
return AI_IS_SLOWER;
|
||||
}
|
||||
|
||||
speedBattlerAI = GetBattlerTotalSpeedStatArgs(battlerAI, abilityAI, holdEffectAI);
|
||||
speedBattler = GetBattlerTotalSpeedStatArgs(battler, abilityPlayer, holdEffectPlayer);
|
||||
|
|
@ -1314,7 +1325,7 @@ s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 moveConsidered)
|
|||
return AI_IS_SLOWER;
|
||||
}
|
||||
|
||||
static bool32 CanEndureHit(u32 battler, u32 battlerTarget, u32 move)
|
||||
bool32 CanEndureHit(u32 battler, u32 battlerTarget, u32 move)
|
||||
{
|
||||
enum BattleMoveEffects effect = GetMoveEffect(move);
|
||||
if (!AI_BattlerAtMaxHp(battlerTarget) || effect == EFFECT_MULTI_HIT)
|
||||
|
|
@ -2043,7 +2054,7 @@ bool32 CanLowerStat(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 stat)
|
|||
if (stat == STAT_SPEED)
|
||||
{
|
||||
// If AI is faster and doesn't have any mons left, lowering speed doesn't give any
|
||||
return !(AI_IsFaster(battlerAtk, battlerDef, gAiThinkingStruct->moveConsidered)
|
||||
return !(AI_IsFaster(battlerAtk, battlerDef, gAiThinkingStruct->moveConsidered, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), DONT_CONSIDER_PRIORITY)
|
||||
&& CountUsablePartyMons(battlerAtk) == 0
|
||||
&& !HasMoveWithEffect(battlerAtk, EFFECT_ELECTRO_BALL));
|
||||
}
|
||||
|
|
@ -2085,7 +2096,7 @@ u32 IncreaseStatDownScore(u32 battlerAtk, u32 battlerDef, u32 stat)
|
|||
tempScore += DECENT_EFFECT;
|
||||
break;
|
||||
case STAT_SPEED:
|
||||
if (AI_IsSlower(battlerAtk, battlerDef, gAiThinkingStruct->moveConsidered))
|
||||
if (AI_IsSlower(battlerAtk, battlerDef, gAiThinkingStruct->moveConsidered, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), DONT_CONSIDER_PRIORITY))
|
||||
tempScore += DECENT_EFFECT;
|
||||
break;
|
||||
case STAT_SPATK:
|
||||
|
|
@ -2191,18 +2202,6 @@ bool32 CanIndexMoveFaintTarget(u32 battlerAtk, u32 battlerDef, u32 moveIndex, en
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
bool32 CanIndexMoveGuaranteeFaintTarget(u32 battlerAtk, u32 battlerDef, u32 moveIndex)
|
||||
{
|
||||
s32 dmg;
|
||||
u16 *moves = gBattleMons[battlerAtk].moves;
|
||||
|
||||
dmg = gAiLogicData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum; // Explictly care about guaranteed KOs universally
|
||||
|
||||
if (gBattleMons[battlerDef].hp <= dmg && !CanEndureHit(battlerAtk, battlerDef, moves[moveIndex]))
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
u16 *GetMovesArray(u32 battler)
|
||||
{
|
||||
if (IsAiBattlerAware(battler) || IsAiBattlerAware(BATTLE_PARTNER(battler)))
|
||||
|
|
@ -3127,7 +3126,7 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 mov
|
|||
if (IsBattlerPredictedToSwitch(battlerDef))
|
||||
return SHOULD_PIVOT; // Try pivoting so you can switch to a better matchup to counter your new opponent
|
||||
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move)) // Attacker goes first
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY)) // Attacker goes first
|
||||
{
|
||||
if (!CanAIFaintTarget(battlerAtk, battlerDef, 0)) // Can't KO foe otherwise
|
||||
{
|
||||
|
|
@ -3512,7 +3511,7 @@ u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbi
|
|||
if (((!IsMoldBreakerTypeAbility(battlerAtk, gAiLogicData->abilities[battlerAtk]) && (defAbility == ABILITY_SHIELD_DUST || defAbility == ABILITY_INNER_FOCUS))
|
||||
|| gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK
|
||||
|| DoesSubstituteBlockMove(battlerAtk, battlerDef, move)
|
||||
|| AI_IsSlower(battlerAtk, battlerDef, move))) // Opponent goes first
|
||||
|| AI_IsSlower(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY))) // Opponent goes first
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -3520,7 +3519,7 @@ u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbi
|
|||
|| gBattleMons[battlerDef].status1 & STATUS1_PARALYSIS
|
||||
|| gBattleMons[battlerDef].volatiles.infatuation
|
||||
|| gBattleMons[battlerDef].volatiles.confusionTurns > 0)
|
||||
|| ((AI_IsFaster(battlerAtk, battlerDef, move)) && CanTargetFaintAi(battlerDef, battlerAtk)))
|
||||
|| ((AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY)) && CanTargetFaintAi(battlerDef, battlerAtk)))
|
||||
{
|
||||
return 2; // good idea to flinch
|
||||
}
|
||||
|
|
@ -3666,7 +3665,7 @@ bool32 ShouldUseRecoilMove(u32 battlerAtk, u32 battlerDef, u32 recoilDmg, u32 mo
|
|||
|
||||
bool32 ShouldAbsorb(u32 battlerAtk, u32 battlerDef, u32 move, s32 damage)
|
||||
{
|
||||
if (move == 0xFFFF || AI_IsFaster(battlerAtk, battlerDef, move))
|
||||
if (move == 0xFFFF || AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY))
|
||||
{
|
||||
// using item or user goes first
|
||||
s32 healDmg = (GetMoveAbsorbPercentage(move) * damage) / 100;
|
||||
|
|
@ -3698,7 +3697,7 @@ bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent)
|
|||
healAmount = maxHP;
|
||||
if (gStatuses3[battlerAtk] & STATUS3_HEAL_BLOCK)
|
||||
healAmount = 0;
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move))
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY))
|
||||
{
|
||||
if (CanTargetFaintAi(battlerDef, battlerAtk)
|
||||
&& !CanTargetFaintAiWithMod(battlerDef, battlerAtk, healAmount, 0))
|
||||
|
|
@ -4040,17 +4039,17 @@ s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct Battl
|
|||
return dmg.median;
|
||||
}
|
||||
|
||||
u32 AI_WhoStrikesFirstPartyMon(u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 moveConsidered)
|
||||
u32 AI_WhoStrikesFirstPartyMon(u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, u32 aiMoveConsidered, u32 playerMoveConsidered, enum ConsiderPriority considerPriority)
|
||||
{
|
||||
struct BattlePokemon *savedBattleMons = AllocSaveBattleMons();
|
||||
gBattleMons[battlerAtk] = switchinCandidate;
|
||||
|
||||
SetBattlerAiData(battlerAtk, gAiLogicData);
|
||||
u32 aiMonFaster = AI_IsFaster(battlerAtk, battlerDef, moveConsidered);
|
||||
u32 aiWhoStrikesFirst = AI_WhoStrikesFirst(battlerAtk, battlerDef, aiMoveConsidered, playerMoveConsidered, considerPriority);
|
||||
FreeRestoreBattleMons(savedBattleMons);
|
||||
SetBattlerAiData(battlerAtk, gAiLogicData);
|
||||
|
||||
return aiMonFaster;
|
||||
return aiWhoStrikesFirst;
|
||||
}
|
||||
|
||||
s32 CountUsablePartyMons(u32 battlerId)
|
||||
|
|
@ -4291,7 +4290,7 @@ static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef,
|
|||
{
|
||||
enum AIScore tempScore = NO_INCREASE;
|
||||
u32 noOfHitsToFaint = NoOfHitsForTargetToFaintBattler(battlerDef, battlerAtk);
|
||||
u32 aiIsFaster = AI_IsFaster(battlerAtk, battlerDef, TRUE);
|
||||
u32 aiIsFaster = AI_IsFaster(battlerAtk, battlerDef, MOVE_NONE, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), DONT_CONSIDER_PRIORITY); // Don't care about the priority of our setup move, care about outspeeding otherwise
|
||||
u32 shouldSetUp = ((noOfHitsToFaint >= 2 && aiIsFaster) || (noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == UNKNOWN_NO_OF_HITS);
|
||||
u32 i;
|
||||
u32 statId = GetStatBeingChanged(statChange);
|
||||
|
|
@ -4841,7 +4840,7 @@ enum AIConsiderGimmick ShouldTeraFromCalcs(u32 battler, u32 opposingBattler, str
|
|||
else
|
||||
{
|
||||
// will we go first?
|
||||
if (AI_WhoStrikesFirst(battler, opposingBattler, killingMove) == AI_IS_FASTER && GetBattleMovePriority(battler, gAiLogicData->abilities[battler], killingMove) >= GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], hardPunishingMove))
|
||||
if (AI_WhoStrikesFirst(battler, opposingBattler, killingMove, GetIncomingMove(battler, opposingBattler, gAiLogicData), CONSIDER_PRIORITY) == AI_IS_FASTER && GetBattleMovePriority(battler, gAiLogicData->abilities[battler], killingMove) >= GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], hardPunishingMove))
|
||||
return USE_GIMMICK;
|
||||
}
|
||||
}
|
||||
|
|
@ -4954,7 +4953,7 @@ void IncreaseTidyUpScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score)
|
|||
if (AreAnyHazardsOnSide(GetBattlerSide(battlerDef)) && CountUsablePartyMons(battlerDef) != 0)
|
||||
ADJUST_SCORE_PTR(-2);
|
||||
|
||||
if (gBattleMons[battlerAtk].volatiles.substitute && AI_IsFaster(battlerAtk, battlerDef, move))
|
||||
if (gBattleMons[battlerAtk].volatiles.substitute && AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), DONT_CONSIDER_PRIORITY))
|
||||
ADJUST_SCORE_PTR(-10);
|
||||
if (gBattleMons[battlerDef].volatiles.substitute)
|
||||
ADJUST_SCORE_PTR(GOOD_EFFECT);
|
||||
|
|
@ -4970,6 +4969,8 @@ bool32 AI_ShouldSpicyExtract(u32 battlerAtk, u32 battlerAtkPartner, u32 move, st
|
|||
u32 preventsStatLoss;
|
||||
u32 partnerAbility;
|
||||
u32 partnerHoldEffect = aiData->holdEffects[battlerAtkPartner];
|
||||
u32 opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battlerAtk));
|
||||
u32 opposingBattler = GetBattlerAtPosition(opposingPosition);
|
||||
|
||||
if (DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move))
|
||||
partnerAbility = ABILITY_NONE;
|
||||
|
|
@ -5002,7 +5003,7 @@ bool32 AI_ShouldSpicyExtract(u32 battlerAtk, u32 battlerAtkPartner, u32 move, st
|
|||
}
|
||||
|
||||
return (preventsStatLoss
|
||||
&& AI_IsFaster(battlerAtk, battlerAtkPartner, TRUE)
|
||||
&& AI_IsFaster(battlerAtk, battlerAtkPartner, MOVE_NONE, GetIncomingMove(battlerAtk, opposingBattler, gAiLogicData), CONSIDER_PRIORITY)
|
||||
&& HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1010,3 +1010,15 @@ AI_SINGLE_BATTLE_TEST("AI will use recovery move if is in no immediate danger be
|
|||
TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_RECOVER); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI has a chance to prioritize last chance priority damage over slow KO")
|
||||
{
|
||||
PASSES_RANDOMLY(PRIORITIZE_LAST_CHANCE_CHANCE, 100, RNG_AI_PRIORITIZE_LAST_CHANCE);
|
||||
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); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_AQUA_JET); }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1164,6 +1164,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if all moves
|
|||
ASSUME(GetMoveType(MOVE_RETURN) == TYPE_NORMAL);
|
||||
ASSUME(GetMoveType(MOVE_DRAIN_PUNCH) == TYPE_FIGHTING);
|
||||
ASSUME(gSpeciesInfo[SPECIES_MAROWAK_ALOLA].types[1] == TYPE_GHOST);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT);
|
||||
PLAYER(SPECIES_MAROWAK_ALOLA) { Ability(ABILITY_LIGHTNING_ROD); Moves(MOVE_SHADOW_BONE); }
|
||||
OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_FAKE_OUT, MOVE_RETURN, MOVE_DRAIN_PUNCH, MOVE_THUNDER_PUNCH); Ability(ABILITY_LIMBER); }
|
||||
OPPONENT(SPECIES_CHANDELURE) { Moves(MOVE_SHADOW_BALL); }
|
||||
|
|
@ -1299,3 +1300,42 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will properly consider immu
|
|||
TURN { MOVE(player, MOVE_KARATE_CHOP); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 2); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI won't switch out due to bad odds if it can OHKO with a priority move")
|
||||
{
|
||||
PASSES_RANDOMLY(100, 100, RNG_AI_SWITCH_HASBADODDS);
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
|
||||
OPPONENT(SPECIES_CETODDLE) { Level(14); HP(30); Speed(1); Moves(MOVE_ICE_FANG, MOVE_ROCK_SMASH, MOVE_BULLDOZE, MOVE_ICE_SHARD); }
|
||||
OPPONENT(SPECIES_SPHEAL) { Level(14); Speed(1); Ability(ABILITY_THICK_FAT); Moves(MOVE_ICY_WIND, MOVE_BRINE, MOVE_HIDDEN_POWER, MOVE_SIGNAL_BEAM); }
|
||||
PLAYER(SPECIES_LITTEN) { Level(15); HP(1); Speed(2); Ability(ABILITY_BLAZE); Moves(MOVE_FIRE_FANG, MOVE_EMBER, MOVE_LICK, MOVE_FAKE_OUT); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_FIRE_FANG); EXPECT_MOVE(opponent, MOVE_ICE_SHARD); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will consider player's priority when evaluating switchin candidates")
|
||||
{
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); HP(1); Moves(MOVE_HEADBUTT); }
|
||||
OPPONENT(SPECIES_ANNIHILAPE) { Speed(5); Moves(MOVE_DRAIN_PUNCH); }
|
||||
OPPONENT(SPECIES_GENGAR) { Speed(10); Moves(MOVE_FOCUS_BLAST); }
|
||||
PLAYER(SPECIES_KINGAMBIT) { Speed(2); Moves(MOVE_SUCKER_PUNCH, MOVE_KNOCK_OFF); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_KNOCK_OFF); EXPECT_MOVE(opponent, MOVE_HEADBUTT); EXPECT_SEND_OUT(opponent, 1); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will consider player's priority when evaluating Bad Odds 1v1")
|
||||
{
|
||||
PASSES_RANDOMLY(SHOULD_SWITCH_HASBADODDS_PERCENTAGE, 100, RNG_AI_SWITCH_HASBADODDS);
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
|
||||
OPPONENT(SPECIES_GENGAR) { Speed(10); Moves(MOVE_FOCUS_BLAST); }
|
||||
OPPONENT(SPECIES_SCRAFTY) { Speed(5); Moves(MOVE_DRAIN_PUNCH); }
|
||||
PLAYER(SPECIES_KINGAMBIT) { Speed(2); Moves(MOVE_SUCKER_PUNCH, MOVE_KNOCK_OFF); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_KNOCK_OFF); EXPECT_SWITCH(opponent, 1); }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user