From d0a35eec1d94085fdfac6f8602700ef36f978867 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 26 Jan 2024 17:48:51 +0100 Subject: [PATCH] AI score changes, mostly IncreaseStatUpScore + few others (#4036) --- include/battle_ai_main.h | 22 ++ include/battle_ai_util.h | 7 +- include/config.h | 2 +- src/battle_ai_main.c | 325 +++++++++----------------- src/battle_ai_util.c | 172 ++++++++------ src/battle_main.c | 1 - test/battle/ai_calc_best_move_score.c | 112 +++++++++ 7 files changed, 348 insertions(+), 293 deletions(-) create mode 100644 test/battle/ai_calc_best_move_score.c diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index 24bc6943aa..3ea4413e7e 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -1,12 +1,34 @@ #ifndef GUARD_BATTLE_AI_MAIN_H #define GUARD_BATTLE_AI_MAIN_H +#define UNKNOWN_NO_OF_HITS UINT32_MAX + // return vals for BattleAI_ChooseMoveOrAction // 0 - 3 are move idx #define AI_CHOICE_FLEE 4 #define AI_CHOICE_WATCH 5 #define AI_CHOICE_SWITCH 7 +// for AI_WhoStrikesFirst +#define AI_IS_FASTER 1 +#define AI_IS_SLOWER -1 + +// for stat increasing / decreasing scores +#define STAT_CHANGE_ATK 0 +#define STAT_CHANGE_DEF 1 +#define STAT_CHANGE_SPEED 2 +#define STAT_CHANGE_SPATK 3 +#define STAT_CHANGE_SPDEF 4 + +#define STAT_CHANGE_ATK_2 5 +#define STAT_CHANGE_DEF_2 6 +#define STAT_CHANGE_SPEED_2 7 +#define STAT_CHANGE_SPATK_2 8 +#define STAT_CHANGE_SPDEF_2 9 + +#define STAT_CHANGE_ACC 10 +#define STAT_CHANGE_EVASION 11 + #include "test_runner.h" // Logs for debugging AI tests. diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 7475ab21ab..d3a99e9625 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -1,10 +1,6 @@ #ifndef GUARD_BATTLE_AI_UTIL_H #define GUARD_BATTLE_AI_UTIL_H -// for AI_WhoStrikesFirst -#define AI_IS_FASTER 1 -#define AI_IS_SLOWER -1 - #define FOE(battler) ((BATTLE_OPPOSITE(battler)) & BIT_SIDE) #define AI_STRIKES_FIRST(battlerAi, battlerDef, move)((AI_WhoStrikesFirst(battlerAi, battlerDef, move) == AI_IS_FASTER)) @@ -32,6 +28,8 @@ u32 GetHealthPercentage(u32 battler); bool32 IsBattlerTrapped(u32 battler, bool32 switching); s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 moveConsidered); bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk); +u32 NoOfHitsForTargetToFaintAI(u32 battlerDef, u32 battlerAtk); +u32 GetBestDmgMoveFromTarget(u32 battlerAtk, u32 battlerDef); bool32 CanTargetMoveFaintAi(u32 move, u32 battlerDef, u32 battlerAtk, u32 nHits); bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dmgMod); s32 AI_DecideKnownAbilityForTurn(u32 battlerId); @@ -130,7 +128,6 @@ bool32 IsAttackBoostMoveEffect(u32 effect); bool32 IsUngroundingEffect(u32 effect); bool32 IsSemiInvulnerable(u32 battlerDef, u32 move); bool32 HasSubstituteIgnoringMove(u32 battler); -bool32 HasSoundMove(u32 battler); bool32 HasHighCritRatioMove(u32 battler); bool32 HasMagicCoatAffectedMove(u32 battler); bool32 HasSnatchAffectedMove(u32 battler); diff --git a/include/config.h b/include/config.h index f4be812643..17dda42411 100644 --- a/include/config.h +++ b/include/config.h @@ -6,7 +6,7 @@ // still has them in the ROM. This is because the developers forgot // to define NDEBUG before release, however this has been changed as // Ruby's actual debug build does not use the AGBPrint features. -#define NDEBUG +// #define NDEBUG // To enable printf debugging, comment out "#define NDEBUG". This allows // the various AGBPrint functions to be used. (See include/gba/isagbprint.h). diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index bd553162fc..e452e96d04 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -402,10 +402,11 @@ static void SetBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u3 u32 battlerDef, i, weather; u16 *moves; - // Simulate dmg for both ai controlled mons and for player controlled mons. SaveBattlerData(battlerAtk); moves = GetMovesArray(battlerAtk); weather = AI_GetWeather(aiData); + + // Simulate dmg for both ai controlled mons and for player controlled mons. for (battlerDef = 0; battlerDef < battlersCount; battlerDef++) { if (battlerAtk == battlerDef) @@ -426,7 +427,6 @@ static void SetBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u3 dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE, weather); aiData->moveAccuracy[battlerAtk][battlerDef][i] = Ai_SetMoveAccuracy(aiData, battlerAtk, battlerDef, move); } - aiData->simulatedDmg[battlerAtk][battlerDef][i] = dmg; aiData->effectiveness[battlerAtk][battlerDef][i] = effectiveness; } @@ -3235,8 +3235,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) { case EFFECT_SLEEP: case EFFECT_YAWN: - if (AI_RandLessThan(128)) - IncreaseSleepScore(battlerAtk, battlerDef, move, &score); + IncreaseSleepScore(battlerAtk, battlerDef, move, &score); break; case EFFECT_ABSORB: if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT && effectiveness >= AI_EFFECTIVENESS_x1) @@ -3253,107 +3252,47 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) if (predictedMove != MOVE_NONE) return AI_CheckViability(battlerAtk, battlerDef, gLastMoves[battlerDef], score); break; -// stat raising effects case EFFECT_ATTACK_UP: - case EFFECT_ATTACK_UP_2: case EFFECT_ATTACK_UP_USER_ALLY: - if (MovesWithCategoryUnusable(battlerAtk, battlerDef, BATTLE_CATEGORY_PHYSICAL)) - { - ADJUST_SCORE(-8); - break; - } - else if (gBattleMons[battlerAtk].statStages[STAT_ATK] < 9) - { - if (aiData->hpPercents[battlerAtk] > 90 && AI_RandLessThan(128)) - { - ADJUST_SCORE(2); - break; - } - } + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); + break; + case EFFECT_ATTACK_UP_2: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK_2, &score); break; case EFFECT_DEFENSE_UP: - case EFFECT_DEFENSE_UP_2: case EFFECT_DEFENSE_UP_3: - if (!HasMoveWithCategory(battlerDef, BATTLE_CATEGORY_PHYSICAL)) - ADJUST_SCORE(-2); - if (aiData->hpPercents[battlerAtk] > 90 && AI_RandLessThan(128)) - ADJUST_SCORE(2); - else if (aiData->hpPercents[battlerAtk] > 70 && AI_RandLessThan(200)) - break; - else if (aiData->hpPercents[battlerAtk] < 40) - ADJUST_SCORE(-2); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + break; + case EFFECT_DEFENSE_UP_2: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF_2, &score); break; case EFFECT_SPEED_UP: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); + break; case EFFECT_SPEED_UP_2: - if (!AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) - { - if (!AI_RandLessThan(70)) - ADJUST_SCORE(3); - } - else - { - ADJUST_SCORE(-3); - } + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED_2, &score); break; case EFFECT_SPECIAL_ATTACK_UP: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK, &score); + break; case EFFECT_SPECIAL_ATTACK_UP_2: case EFFECT_SPECIAL_ATTACK_UP_3: - if (MovesWithCategoryUnusable(battlerAtk, battlerDef, BATTLE_CATEGORY_SPECIAL)) - { - ADJUST_SCORE(-8); - break; - } - else if (gBattleMons[battlerAtk].statStages[STAT_SPATK] < 9) - { - if (aiData->hpPercents[battlerAtk] > 90 && AI_RandLessThan(128)) - { - ADJUST_SCORE(2); - break; - } - } + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK_2, &score); break; case EFFECT_SPECIAL_DEFENSE_UP: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); + break; case EFFECT_SPECIAL_DEFENSE_UP_2: - if (!HasMoveWithCategory(battlerDef, BATTLE_CATEGORY_SPECIAL)) - ADJUST_SCORE(-2); - if (aiData->hpPercents[battlerAtk] > 90 && AI_RandLessThan(128)) - ADJUST_SCORE(2); - else if (aiData->hpPercents[battlerAtk] > 70 && AI_RandLessThan(200)) - break; - else if (aiData->hpPercents[battlerAtk] < 40) - ADJUST_SCORE(-2); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF_2, &score); break; case EFFECT_ACCURACY_UP: case EFFECT_ACCURACY_UP_2: - if (gBattleMons[battlerAtk].statStages[STAT_ACC] >= 9 && !AI_RandLessThan(50)) - ADJUST_SCORE(-2); - else if (aiData->hpPercents[battlerAtk] <= 70) - ADJUST_SCORE(-2); - else - ADJUST_SCORE(1); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ACC, &score); break; case EFFECT_EVASION_UP: case EFFECT_EVASION_UP_2: - if (aiData->hpPercents[battlerAtk] > 90 && !AI_RandLessThan(100)) - ADJUST_SCORE(3); - if (gBattleMons[battlerAtk].statStages[STAT_EVASION] > 9 && AI_RandLessThan(128)) - ADJUST_SCORE(-1); - if ((gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY) && aiData->hpPercents[battlerAtk] >= 50 && !AI_RandLessThan(80)) - ADJUST_SCORE(3); - if (gStatuses3[battlerDef] & STATUS3_LEECHSEED && !AI_RandLessThan(70)) - ADJUST_SCORE(3); - if (gStatuses3[battlerAtk] & STATUS3_ROOTED && AI_RandLessThan(128)) - ADJUST_SCORE(2); - if (gBattleMons[battlerDef].status2 & STATUS2_CURSED && !AI_RandLessThan(70)) - ADJUST_SCORE(3); - if (aiData->hpPercents[battlerAtk] < 70 || gBattleMons[battlerAtk].statStages[STAT_EVASION] == DEFAULT_STAT_STAGE) - break; - else if (aiData->hpPercents[battlerAtk] < 40 || aiData->hpPercents[battlerDef] < 40) - ADJUST_SCORE(-2); - else if (!AI_RandLessThan(70)) - ADJUST_SCORE(-2); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_EVASION, &score); break; - // stat lowering effects case EFFECT_ATTACK_DOWN: case EFFECT_ATTACK_DOWN_2: if (!ShouldLowerAttack(battlerAtk, battlerDef, aiData->abilities[battlerDef])) @@ -3442,39 +3381,27 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) break; case EFFECT_BIDE: if (aiData->hpPercents[battlerAtk] < 90) - ADJUST_SCORE(-2); - break; + ADJUST_SCORE(-2); // Should be either removed or turned into increasing score case EFFECT_ACUPRESSURE: break; case EFFECT_ATTACK_ACCURACY_UP: // hone claws - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_ATK, &score); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_ACC, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ACC, &score); break; case EFFECT_GROWTH: case EFFECT_ATTACK_SPATK_UP: // work up - if (aiData->hpPercents[battlerAtk] <= 40 || aiData->abilities[battlerAtk] == ABILITY_CONTRARY) - break; - - if (HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_PHYSICAL)) - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_ATK, &score); - else if (HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_SPECIAL)) - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK, &score); break; case EFFECT_HAZE: if (AnyStatIsRaised(BATTLE_PARTNER(battlerAtk)) || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) - { - ADJUST_SCORE(-3); break; - } score += AI_TryToClearStats(battlerAtk, battlerDef, isDoubleBattle); break; case EFFECT_ROAR: if ((gBattleMoves[move].soundMove && aiData->abilities[battlerDef] == ABILITY_SOUNDPROOF) || aiData->abilities[battlerDef] == ABILITY_SUCTION_CUPS) - { - ADJUST_SCORE(-3); break; - } score += AI_TryToClearStats(battlerAtk, battlerDef, isDoubleBattle); break; case EFFECT_MULTI_HIT: @@ -3570,8 +3497,8 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(5); break; case EFFECT_MEAN_LOOK: - if (!IsBattlerTrapped(battlerDef, TRUE) && ShouldTrap(battlerAtk, battlerDef, move)) - ADJUST_SCORE(5); + if (ShouldTrap(battlerAtk, battlerDef, move)) + ADJUST_SCORE(2); break; case EFFECT_MIST: if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_SCREENER) @@ -3628,7 +3555,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_DO_NOTHING: //todo - check z splash, z celebrate, z happy hour (lol) break; - case EFFECT_TELEPORT: + case EFFECT_TELEPORT: // Either remove or add better logic if (!(gBattleTypeFlags & BATTLE_TYPE_TRAINER) || GetBattlerSide(battlerAtk) != B_SIDE_PLAYER) break; //fallthrough @@ -3689,7 +3616,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) && (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)) { if (gBattleMoveEffects[gBattleMoves[gLastMoves[battlerDef]].effect].encourageEncore) - ADJUST_SCORE(3); + ADJUST_SCORE(6); } break; case EFFECT_PAIN_SPLIT: @@ -3845,7 +3772,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_SANDSTORM: if (ShouldSetSandstorm(battlerAtk, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerAtk])) { - ADJUST_SCORE(1); + ADJUST_SCORE(2); if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_SMOOTH_ROCK) ADJUST_SCORE(1); if (HasMoveEffect(battlerDef, EFFECT_MORNING_SUN) @@ -3861,7 +3788,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) && ShouldSetScreen(battlerAtk, battlerDef, EFFECT_AURORA_VEIL)) ADJUST_SCORE(3); - ADJUST_SCORE(1); + ADJUST_SCORE(2); if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_ICY_ROCK) ADJUST_SCORE(1); if (HasMoveEffect(battlerDef, EFFECT_MORNING_SUN) @@ -3877,7 +3804,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) && ShouldSetScreen(battlerAtk, battlerDef, EFFECT_AURORA_VEIL)) ADJUST_SCORE(3); - ADJUST_SCORE(1); + ADJUST_SCORE(2); if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_ICY_ROCK) ADJUST_SCORE(1); if (HasMoveEffect(battlerDef, EFFECT_MORNING_SUN) @@ -3889,7 +3816,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_RAIN_DANCE: if (ShouldSetRain(battlerAtk, aiData->abilities[battlerAtk], aiData->holdEffects[battlerAtk])) { - ADJUST_SCORE(1); + ADJUST_SCORE(2); if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_DAMP_ROCK) ADJUST_SCORE(1); if (HasMoveEffect(battlerDef, EFFECT_MORNING_SUN) @@ -3904,7 +3831,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_SUNNY_DAY: if (ShouldSetSun(battlerAtk, aiData->abilities[battlerAtk], aiData->holdEffects[battlerAtk])) { - ADJUST_SCORE(1); + ADJUST_SCORE(2); if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_HEAT_ROCK) ADJUST_SCORE(1); if (HasMoveWithType(battlerDef, TYPE_WATER) || HasMoveWithType(BATTLE_PARTNER(battlerDef), TYPE_WATER)) @@ -3915,18 +3842,16 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) break; case EFFECT_FELL_STINGER: if (gBattleMons[battlerAtk].statStages[STAT_ATK] < MAX_STAT_STAGE - && aiData->abilities[battlerAtk] != ABILITY_CONTRARY - && CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, 0)) - { - if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker goes first - ADJUST_SCORE(9); - else - ADJUST_SCORE(3); - } + && aiData->abilities[battlerAtk] != ABILITY_CONTRARY + && CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, 0)) + ADJUST_SCORE(10); break; case EFFECT_BELLY_DRUM: - if (!CanTargetFaintAi(battlerDef, battlerAtk) && HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_PHYSICAL) && aiData->abilities[battlerAtk] != ABILITY_CONTRARY) - score += (MAX_STAT_STAGE - gBattleMons[battlerAtk].statStages[STAT_ATK]); + if (!CanTargetFaintAi(battlerDef, battlerAtk) + && gBattleMons[battlerAtk].statStages[STAT_ATK] < MAX_STAT_STAGE - 2 + && HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_PHYSICAL) + && aiData->abilities[battlerAtk] != ABILITY_CONTRARY) + ADJUST_SCORE(10); break; case EFFECT_PSYCH_UP: score += AI_ShouldCopyStatChanges(battlerAtk, battlerDef); @@ -3950,7 +3875,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_DEFENSE_CURL: if (HasMoveEffect(battlerAtk, EFFECT_ROLLOUT) && !(gBattleMons[battlerAtk].status2 & STATUS2_DEFENSE_CURL)) ADJUST_SCORE(1); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_DEF, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); break; case EFFECT_FAKE_OUT: if (move == MOVE_FAKE_OUT) // filter out first impression @@ -3964,28 +3889,17 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_STOCKPILE: if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) break; - if (HasMoveEffect(battlerAtk, EFFECT_SWALLOW) - || HasMoveEffect(battlerAtk, EFFECT_SPIT_UP)) + if (HasMoveEffect(battlerAtk, EFFECT_SWALLOW) || HasMoveEffect(battlerAtk, EFFECT_SPIT_UP)) ADJUST_SCORE(2); - - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_DEF, &score); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPDEF, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); break; case EFFECT_SWAGGER: - if (HasMoveEffect(battlerAtk, EFFECT_FOUL_PLAY) - || HasMoveEffect(battlerAtk, EFFECT_PSYCH_UP) - || HasMoveWithMoveEffect(battlerAtk, MOVE_EFFECT_SPECTRAL_THIEF)) - ADJUST_SCORE(1); - - if (aiData->abilities[battlerDef] == ABILITY_CONTRARY) - ADJUST_SCORE(2); - - IncreaseConfusionScore(battlerAtk, battlerDef, move, &score); - break; case EFFECT_FLATTER: - if (HasMoveEffect(battlerAtk, EFFECT_PSYCH_UP) - || HasMoveWithMoveEffect(battlerAtk, MOVE_EFFECT_SPECTRAL_THIEF)) - ADJUST_SCORE(2); + if (HasMoveEffect(battlerAtk, EFFECT_FOUL_PLAY) + || HasMoveEffect(battlerAtk, EFFECT_PSYCH_UP) + || HasMoveWithMoveEffect(battlerAtk, MOVE_EFFECT_SPECTRAL_THIEF)) + ADJUST_SCORE(1); if (aiData->abilities[battlerDef] == ABILITY_CONTRARY) ADJUST_SCORE(2); @@ -3997,13 +3911,13 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(3); break; case EFFECT_ATTRACT: - if (!isDoubleBattle && BattlerWillFaintFromSecondaryDamage(battlerDef, aiData->abilities[battlerDef]) - && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Target goes first + if (!isDoubleBattle + && (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) + && 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 - || (gBattleMons[battlerDef].status2 & STATUS2_CONFUSION) - || IsBattlerTrapped(battlerDef, TRUE)) + || (gBattleMons[battlerDef].status2 & STATUS2_CONFUSION) + || IsBattlerTrapped(battlerDef, TRUE)) ADJUST_SCORE(2); else ADJUST_SCORE(1); @@ -4058,13 +3972,11 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(3); } break; - case EFFECT_NATURE_POWER: - return AI_CheckViability(battlerAtk, battlerDef, GetNaturePowerMove(), score); case EFFECT_CHARGE: if (HasDamagingMoveOfType(battlerAtk, TYPE_ELECTRIC)) ADJUST_SCORE(2); if (B_CHARGE_SPDEF_RAISE >= GEN_5) - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPDEF, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); break; case EFFECT_TAUNT: if (IS_MOVE_STATUS(predictedMove)) @@ -4200,15 +4112,15 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) break; case EFFECT_BRICK_BREAK: if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_REFLECT) - ADJUST_SCORE(1); + ADJUST_SCORE(2); if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_LIGHTSCREEN) - ADJUST_SCORE(1); + ADJUST_SCORE(2); if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_AURORA_VEIL) - ADJUST_SCORE(1); + ADJUST_SCORE(2); break; case EFFECT_SKILL_SWAP: if (gAbilities[aiData->abilities[battlerDef]].aiRating > gAbilities[aiData->abilities[battlerAtk]].aiRating) - ADJUST_SCORE(1); + ADJUST_SCORE(2); break; case EFFECT_WORRY_SEED: case EFFECT_GASTRO_ACID: @@ -4277,43 +4189,42 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) } break; case EFFECT_COSMIC_POWER: - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_DEF, &score); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPDEF, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); break; case EFFECT_BULK_UP: - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_ATK, &score); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_DEF, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); break; case EFFECT_CALM_MIND: - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPATK, &score); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPDEF, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); break; case EFFECT_GEOMANCY: if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_POWER_HERB) ADJUST_SCORE(3); - //fallthrough case EFFECT_QUIVER_DANCE: - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPEED, &score); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPATK, &score); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPDEF, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); break; case EFFECT_VICTORY_DANCE: - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPEED, &score); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_ATK, &score); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_DEF, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); break; case EFFECT_SHELL_SMASH: if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_RESTORE_STATS) ADJUST_SCORE(1); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPEED, &score); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPATK, &score); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_ATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); break; case EFFECT_DRAGON_DANCE: case EFFECT_SHIFT_GEAR: - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPEED, &score); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_ATK, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); break; case EFFECT_GUARD_SWAP: if (gBattleMons[battlerDef].statStages[STAT_DEF] > gBattleMons[battlerAtk].statStages[STAT_DEF] @@ -4332,12 +4243,10 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(1); break; case EFFECT_POWER_TRICK: - if (!(gStatuses3[battlerAtk] & STATUS3_POWER_TRICK)) - { - if (gBattleMons[battlerAtk].defense > gBattleMons[battlerAtk].attack && HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_PHYSICAL)) - ADJUST_SCORE(2); - break; - } + if (!(gStatuses3[battlerAtk] & STATUS3_POWER_TRICK) + && gBattleMons[battlerAtk].defense > gBattleMons[battlerAtk].attack + && HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_PHYSICAL)) + ADJUST_SCORE(2); break; case EFFECT_HEART_SWAP: { @@ -4505,11 +4414,8 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(1); break; case EFFECT_FAIRY_LOCK: - if (!IsBattlerTrapped(battlerDef, TRUE)) - { - if (ShouldTrap(battlerAtk, battlerDef, move)) - ADJUST_SCORE(8); - } + if (ShouldTrap(battlerAtk, battlerDef, move)) + ADJUST_SCORE(8); break; case EFFECT_QUASH: if (isDoubleBattle @@ -4556,7 +4462,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) break; case EFFECT_TOXIC_THREAD: IncreasePoisonScore(battlerAtk, battlerDef, move, &score); - IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPEED, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); break; case EFFECT_COUNTER: if (!IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) && predictedMove != MOVE_NONE) @@ -4638,6 +4544,9 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) // Consider move effects that target self if (gBattleMoves[move].additionalEffects[i].self) { + u32 oneStageStatId = STAT_CHANGE_ATK + gBattleMoves[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1; + u32 twoStageStatId = STAT_CHANGE_ATK_2 + gBattleMoves[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1; + switch (gBattleMoves[move].additionalEffects[i].moveEffect) { case MOVE_EFFECT_SPD_PLUS_2: @@ -4651,12 +4560,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) case MOVE_EFFECT_SP_DEF_PLUS_1: case MOVE_EFFECT_ACC_PLUS_1: case MOVE_EFFECT_EVS_PLUS_1: - IncreaseStatUpScore( - battlerAtk, - battlerDef, - STAT_ATK + gBattleMoves[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1, - &score - ); + IncreaseStatUpScore(battlerAtk, battlerDef, oneStageStatId, &score); break; case MOVE_EFFECT_ATK_PLUS_2: case MOVE_EFFECT_DEF_PLUS_2: @@ -4664,12 +4568,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) case MOVE_EFFECT_SP_DEF_PLUS_2: case MOVE_EFFECT_ACC_PLUS_2: case MOVE_EFFECT_EVS_PLUS_2: - IncreaseStatUpScore( - battlerAtk, - battlerDef, - STAT_ATK + gBattleMoves[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_2, - &score - ); + IncreaseStatUpScore(battlerAtk, battlerDef, twoStageStatId, &score); break; // Effects that lower stat(s) - only need to consider Contrary case MOVE_EFFECT_ATK_MINUS_1: @@ -4677,23 +4576,23 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) case MOVE_EFFECT_SPD_MINUS_1: case MOVE_EFFECT_SP_ATK_MINUS_1: case MOVE_EFFECT_SP_DEF_MINUS_1: - case MOVE_EFFECT_V_CREATE: case MOVE_EFFECT_DEF_SPDEF_DOWN: case MOVE_EFFECT_ATK_DEF_DOWN: case MOVE_EFFECT_SP_ATK_TWO_DOWN: if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) - ADJUST_SCORE(3); + IncreaseStatUpScore(battlerAtk, battlerDef, oneStageStatId, &score); + case MOVE_EFFECT_V_CREATE: + if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) + { + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); + } break; case MOVE_EFFECT_RAPIDSPIN: if ((gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY && CountUsablePartyMons(battlerAtk) != 0) - || (gStatuses3[battlerAtk] & STATUS3_LEECHSEED || gBattleMons[battlerAtk].status2 & STATUS2_WRAPPED)) - { + || (gStatuses3[battlerAtk] & STATUS3_LEECHSEED || gBattleMons[battlerAtk].status2 & STATUS2_WRAPPED)) ADJUST_SCORE(3); - break; - } - //Spin checks - if (!(gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY)) - ADJUST_SCORE(-6); break; } } @@ -4830,21 +4729,16 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(3); break; case MOVE_EFFECT_THROAT_CHOP: - if (HasSoundMove(battlerDef) && gBattleMoves[predictedMove].soundMove && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) - ADJUST_SCORE(3); - break; - case MOVE_EFFECT_FLAME_BURST: - if (isDoubleBattle) + if (gBattleMoves[GetBestDmgMoveFromTarget(battlerDef, battlerAtk)].soundMove) { - if (IsBattlerAlive(BATTLE_PARTNER(battlerDef)) - && aiData->hpPercents[BATTLE_PARTNER(battlerDef)] < 12 - && aiData->abilities[BATTLE_PARTNER(battlerDef)] != ABILITY_MAGIC_GUARD - && !IS_BATTLER_OF_TYPE(BATTLE_PARTNER(battlerDef), TYPE_FIRE)) - ADJUST_SCORE(1); + if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) + ADJUST_SCORE(4); + else + ADJUST_SCORE(2); } break; case MOVE_EFFECT_WRAP: - if (!HasMoveWithMoveEffect(battlerDef, MOVE_EFFECT_RAPIDSPIN) && !IsBattlerTrapped(battlerDef, TRUE) && ShouldTrap(battlerAtk, battlerDef, move)) + if (!HasMoveWithMoveEffect(battlerDef, MOVE_EFFECT_RAPIDSPIN) && ShouldTrap(battlerAtk, battlerDef, move)) ADJUST_SCORE(5); break; } @@ -5075,8 +4969,6 @@ static s32 AI_PreferStrongestMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 // Prefers moves that are good for baton pass static s32 AI_PreferBatonPass(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { - u32 i; - if (IS_TARGETING_PARTNER(battlerAtk, battlerDef) || CountUsablePartyMons(battlerAtk) == 0 || gBattleMoves[move].power != 0 @@ -5112,10 +5004,7 @@ static s32 AI_PreferBatonPass(u32 battlerAtk, u32 battlerDef, u32 move, s32 scor ADJUST_SCORE(2); break; case EFFECT_BATON_PASS: - for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) - { - IncreaseStatUpScore(battlerAtk, battlerDef, i, &score); - } + // TODO: Increase Score based on current stats. if (gStatuses3[battlerAtk] & (STATUS3_ROOTED | STATUS3_AQUA_RING)) ADJUST_SCORE(2); if (gStatuses3[battlerAtk] & STATUS3_LEECHSEED) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 96d79bb256..99cc0c187b 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -499,6 +499,7 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes gBattleStruct->swapDamageCategory = FALSE; gBattleStruct->zmove.active = FALSE; gBattleStruct->zmove.baseMoves[battlerAtk] = MOVE_NONE; + return dmg; } @@ -847,6 +848,46 @@ bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk) return FALSE; } +u32 NoOfHitsForTargetToFaintAI(u32 battlerDef, u32 battlerAtk) +{ + u32 i; + u32 currNumberOfHits; + u32 leastNumberOfHits = UNKNOWN_NO_OF_HITS; + + for (i = 0; i < MAX_MON_MOVES; i++) + { + currNumberOfHits = GetNoOfHitsToKOBattler(battlerDef, battlerAtk, i); + if (currNumberOfHits != 0) + { + if (currNumberOfHits < leastNumberOfHits) + leastNumberOfHits = currNumberOfHits; + else if (leastNumberOfHits == UNKNOWN_NO_OF_HITS) + leastNumberOfHits = currNumberOfHits; + } + } + return leastNumberOfHits; +} + +u32 GetBestDmgMoveFromTarget(u32 battlerDef, u32 battlerAtk) +{ + u32 i; + u32 move = 0; + u32 bestDmg = 0; + u32 unusable = AI_DATA->moveLimitations[battlerDef]; + u16 *moves = GetMovesArray(battlerDef); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & gBitTable[i]) + && bestDmg < AI_DATA->simulatedDmg[battlerDef][battlerAtk][i]) + { + bestDmg = AI_DATA->simulatedDmg[battlerDef][battlerAtk][i]; + move = moves[i]; + } + } + return move; +} + // Check if AI mon has the means to faint the target with any of its moves. // If numHits > 1, check if the target will be KO'ed by that number of hits (ignoring healing effects) bool32 CanAIFaintTarget(u32 battlerAtk, u32 battlerDef, u32 numHits) @@ -1951,11 +1992,6 @@ bool32 HasSubstituteIgnoringMove(u32 battler) CHECK_MOVE_FLAG(ignoresSubstitute); } -bool32 HasSoundMove(u32 battler) -{ - CHECK_MOVE_FLAG(soundMove); -} - bool32 HasHighCritRatioMove(u32 battler) { s32 i; @@ -2672,6 +2708,9 @@ u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbi bool32 ShouldTrap(u32 battlerAtk, u32 battlerDef, u32 move) { + if (IsBattlerTrapped(battlerDef, TRUE)) + return FALSE; + if (BattlerWillFaintFromSecondaryDamage(battlerDef, AI_DATA->abilities[battlerDef])) return TRUE; // battler is taking secondary damage with low HP @@ -3243,85 +3282,82 @@ bool32 IsRecycleEncouragedItem(u32 item) return FALSE; } -// score increases -#define STAT_UP_2_STAGE 8 -#define STAT_UP_STAGE 10 void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score) { + u32 noOfHitsToFaint = NoOfHitsForTargetToFaintAI(battlerDef, battlerAtk); + u32 aiIsFaster = GetWhichBattlerFaster(battlerAtk, battlerDef, TRUE) == AI_IS_FASTER; + u32 shouldSetUp = ((noOfHitsToFaint >= 2 && aiIsFaster) || (noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == UNKNOWN_NO_OF_HITS); + if (AI_DATA->abilities[battlerAtk] == ABILITY_CONTRARY) return; - if (AI_DATA->hpPercents[battlerAtk] < 80 && AI_RandLessThan(128)) + // Don't increase stat if AI is at +4 + if (gBattleMons[battlerAtk].statStages[statId] >= MAX_STAT_STAGE - 2) return; - if ((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) - return; // Damaging moves would get a score boost from AI_TryToFaint or PreferStrongestMove so we don't consider them here + // Don't increase stat if AI has less then 70% HP and number of hits isn't known + if (AI_DATA->hpPercents[battlerAtk] < 70 && noOfHitsToFaint == UNKNOWN_NO_OF_HITS) + return; + + // Don't set up if AI is dead to residual damage from weather + if (BattlerWillFaintFromWeather(battlerAtk, AI_DATA->abilities[battlerAtk])) + return; + + // Don't increase stats if opposing battler has Opportunist + if (AI_DATA->abilities[battlerDef] == ABILITY_OPPORTUNIST) + return; switch (statId) { - case STAT_ATK: - if (HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_PHYSICAL) && AI_DATA->hpPercents[battlerAtk] > 40) - { - if (gBattleMons[battlerAtk].statStages[STAT_ATK] < STAT_UP_2_STAGE) - ADJUST_SCORE_PTR(2); - else if (gBattleMons[battlerAtk].statStages[STAT_ATK] < STAT_UP_STAGE) - ADJUST_SCORE_PTR(1); - } - if (HasMoveEffect(battlerAtk, EFFECT_FOUL_PLAY)) - ADJUST_SCORE_PTR(1); + case STAT_CHANGE_ATK: + if (HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_PHYSICAL) && shouldSetUp) + ADJUST_SCORE_PTR(2); break; - case STAT_DEF: - if ((HasMoveWithCategory(battlerDef, BATTLE_CATEGORY_PHYSICAL)|| IS_MOVE_PHYSICAL(gLastMoves[battlerDef])) - && AI_DATA->hpPercents[battlerAtk] > 70) - { - if (gBattleMons[battlerAtk].statStages[STAT_DEF] < STAT_UP_2_STAGE) - ADJUST_SCORE_PTR(2); // seems better to raise def at higher HP - else if (gBattleMons[battlerAtk].statStages[STAT_DEF] < STAT_UP_STAGE) - ADJUST_SCORE_PTR(1); - } + case STAT_CHANGE_DEF: + if (HasMoveWithCategory(battlerDef, BATTLE_CATEGORY_PHYSICAL) || !HasMoveWithCategory(battlerDef, BATTLE_CATEGORY_SPECIAL)) + ADJUST_SCORE_PTR(2); break; - case STAT_SPEED: - if (!AI_STRIKES_FIRST(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered)) - { - if (gBattleMons[battlerAtk].statStages[STAT_SPEED] < STAT_UP_2_STAGE) - ADJUST_SCORE_PTR(2); - else if (gBattleMons[battlerAtk].statStages[STAT_SPEED] < STAT_UP_STAGE) - ADJUST_SCORE_PTR(1); - } + case STAT_CHANGE_SPEED: + if ((noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == 0) + ADJUST_SCORE_PTR(2); break; - case STAT_SPATK: - if (HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_SPECIAL) && AI_DATA->hpPercents[battlerAtk] > 40) - { - if (gBattleMons[battlerAtk].statStages[STAT_SPATK] < STAT_UP_2_STAGE) - ADJUST_SCORE_PTR(2); - else if (gBattleMons[battlerAtk].statStages[STAT_SPATK] < STAT_UP_STAGE) - ADJUST_SCORE_PTR(1); - } + case STAT_CHANGE_SPATK: + if (HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_SPECIAL) && shouldSetUp) + ADJUST_SCORE_PTR(2); break; - case STAT_SPDEF: - if ((HasMoveWithCategory(battlerDef, BATTLE_CATEGORY_SPECIAL) || IS_MOVE_SPECIAL(gLastMoves[battlerDef])) - && AI_DATA->hpPercents[battlerAtk] > 70) - { - if (gBattleMons[battlerAtk].statStages[STAT_SPDEF] < STAT_UP_2_STAGE) - ADJUST_SCORE_PTR(2); // seems better to raise spdef at higher HP - else if (gBattleMons[battlerAtk].statStages[STAT_SPDEF] < STAT_UP_STAGE) - ADJUST_SCORE_PTR(1); - } + case STAT_CHANGE_SPDEF: + if (HasMoveWithCategory(battlerDef, BATTLE_CATEGORY_SPECIAL) || !HasMoveWithCategory(battlerDef, BATTLE_CATEGORY_PHYSICAL)) + ADJUST_SCORE_PTR(2); break; - case STAT_ACC: - if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 80, TRUE, AI_DATA->abilities[battlerAtk], AI_DATA->abilities[battlerDef], AI_DATA->holdEffects[battlerAtk], AI_DATA->holdEffects[battlerDef])) - ADJUST_SCORE_PTR(2); // has moves with less than 80% accuracy - else if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 90, TRUE, AI_DATA->abilities[battlerAtk], AI_DATA->abilities[battlerDef], AI_DATA->holdEffects[battlerAtk], AI_DATA->holdEffects[battlerDef])) - ADJUST_SCORE_PTR(1); + case STAT_CHANGE_ATK_2: + if (HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_PHYSICAL) && shouldSetUp) + ADJUST_SCORE_PTR(4); break; - case STAT_EVASION: - if (!BattlerWillFaintFromWeather(battlerAtk, AI_DATA->abilities[battlerAtk])) - { - if (!GetBattlerSecondaryDamage(battlerAtk) && !(gStatuses3[battlerAtk] & STATUS3_ROOTED)) - ADJUST_SCORE_PTR(2); - else - ADJUST_SCORE_PTR(1); - } + case STAT_CHANGE_DEF_2: + if (HasMoveWithCategory(battlerDef, BATTLE_CATEGORY_PHYSICAL) || !HasMoveWithCategory(battlerDef, BATTLE_CATEGORY_SPECIAL)) + ADJUST_SCORE_PTR(4); + break; + case STAT_CHANGE_SPEED_2: + if ((noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == 0) + ADJUST_SCORE_PTR(4); + break; + case STAT_CHANGE_SPATK_2: + if (HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_SPECIAL) && shouldSetUp) + ADJUST_SCORE_PTR(4); + break; + case STAT_CHANGE_SPDEF_2: + if (HasMoveWithCategory(battlerDef, BATTLE_CATEGORY_SPECIAL) || !HasMoveWithCategory(battlerDef, BATTLE_CATEGORY_PHYSICAL)) + ADJUST_SCORE_PTR(4); + break; + case STAT_CHANGE_ACC: + if (gBattleMons[battlerAtk].statStages[STAT_ACC] <= 3) // Increase only if necessary + ADJUST_SCORE_PTR(2); + break; + case STAT_CHANGE_EVASION: + if (GetBattlerSecondaryDamage(battlerAtk) && ((noOfHitsToFaint > 3) || noOfHitsToFaint == 0)) + ADJUST_SCORE_PTR(4); + else + ADJUST_SCORE_PTR(2); break; } } diff --git a/src/battle_main.c b/src/battle_main.c index d36f8ff386..01c751d1fc 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4804,7 +4804,6 @@ s32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMov { strikesFirst = 1; // battler1's move has greater priority } - return strikesFirst; } diff --git a/test/battle/ai_calc_best_move_score.c b/test/battle/ai_calc_best_move_score.c new file mode 100644 index 0000000000..e798ed3299 --- /dev/null +++ b/test/battle/ai_calc_best_move_score.c @@ -0,0 +1,112 @@ +#include "global.h" +#include "test/battle.h" +#include "battle_ai_util.h" + +AI_SINGLE_BATTLE_TEST("AI will not further increase Attack / Sp. Atk stat if it knows it faints to target: AI faster") +{ + u16 move; + + PARAMETRIZE { move = MOVE_HOWL; } + PARAMETRIZE { move = MOVE_CALM_MIND; } + + GIVEN { + ASSUME(gBattleMoves[MOVE_SKY_UPPERCUT].power == 85); + ASSUME(gBattleMoves[MOVE_HOWL].effect == EFFECT_ATTACK_UP_USER_ALLY); + ASSUME(gBattleMoves[MOVE_CALM_MIND].effect == EFFECT_CALM_MIND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_COMBUSKEN) { Speed(15); Moves(MOVE_SKY_UPPERCUT, MOVE_CELEBRATE); }; + OPPONENT(SPECIES_KANGASKHAN) { Speed(20); Moves(MOVE_CHIP_AWAY, MOVE_SWIFT, move); } + } WHEN { + TURN { MOVE(player, MOVE_SKY_UPPERCUT); EXPECT_MOVE(opponent, move); } + TURN { EXPECT_MOVE(opponent, MOVE_CHIP_AWAY); MOVE(player, MOVE_SKY_UPPERCUT); } + } +} + +AI_SINGLE_BATTLE_TEST("AI will not further increase Attack / Sp. Atk stat if it knows it faints to target: AI slower") +{ + u16 move; + + PARAMETRIZE { move = MOVE_HOWL; } + PARAMETRIZE { move = MOVE_CALM_MIND; } + + GIVEN { + ASSUME(gBattleMoves[MOVE_SKY_UPPERCUT].power == 85); + ASSUME(gBattleMoves[MOVE_HOWL].effect == EFFECT_ATTACK_UP_USER_ALLY); + ASSUME(gBattleMoves[MOVE_CALM_MIND].effect == EFFECT_CALM_MIND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_COMBUSKEN) { Speed(20); Moves(MOVE_DOUBLE_KICK, MOVE_CELEBRATE); }; + OPPONENT(SPECIES_KANGASKHAN) { Speed(15); Moves(MOVE_CHIP_AWAY, MOVE_SWIFT, move); } + } WHEN { + TURN { MOVE(player, MOVE_DOUBLE_KICK); EXPECT_MOVE(opponent, move); } + TURN { EXPECT_MOVE(opponent, MOVE_CHIP_AWAY); MOVE(player, MOVE_DOUBLE_KICK); } + } +} + +AI_SINGLE_BATTLE_TEST("AI will increase speed if it is slower") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_COMBUSKEN) { Speed(20); Moves(MOVE_DOUBLE_KICK, MOVE_CELEBRATE); }; + OPPONENT(SPECIES_KANGASKHAN) { Speed(15); Moves(MOVE_CHIP_AWAY, MOVE_AGILITY); } + } WHEN { + TURN { MOVE(player, MOVE_DOUBLE_KICK); EXPECT_MOVE(opponent, MOVE_AGILITY); } + TURN { EXPECT_MOVE(opponent, MOVE_CHIP_AWAY); MOVE(player, MOVE_DOUBLE_KICK); } + } +} + +AI_SINGLE_BATTLE_TEST("AI will correctly predict what move the opposing mon going to use") +{ + u16 move; + + PARAMETRIZE { move = MOVE_HOWL; } + PARAMETRIZE { move = MOVE_CALM_MIND; } + + KNOWN_FAILING; + GIVEN { + ASSUME(gBattleMoves[MOVE_SKY_UPPERCUT].power == 85); + ASSUME(gBattleMoves[MOVE_HOWL].effect == EFFECT_ATTACK_UP_USER_ALLY); + ASSUME(gBattleMoves[MOVE_CALM_MIND].effect == EFFECT_CALM_MIND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_COMBUSKEN) { Speed(15); Moves(MOVE_SKY_UPPERCUT, MOVE_DOUBLE_KICK, MOVE_FLAME_WHEEL, MOVE_CELEBRATE); }; + OPPONENT(SPECIES_KANGASKHAN) { Speed(20); Moves(MOVE_CHIP_AWAY, MOVE_SWIFT, move); } + } WHEN { + TURN { MOVE(player, MOVE_DOUBLE_KICK); EXPECT_MOVE(opponent, move); } + TURN { EXPECT_MOVE(opponent, MOVE_CHIP_AWAY); MOVE(player, MOVE_SKY_UPPERCUT); } + } +} + +AI_SINGLE_BATTLE_TEST("AI will not use Throat Chop if opposing mon has a better move") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_PSYCHIC_FANGS].power == 85); + ASSUME(gBattleMoves[MOVE_THROAT_CHOP].power == 80); + ASSUME(gBattleMoves[MOVE_DISARMING_VOICE].power == 40); + ASSUME(gBattleMoves[MOVE_FLAME_BURST].power == 70); + ASSUME(MoveHasMoveEffect(MOVE_THROAT_CHOP, MOVE_EFFECT_THROAT_CHOP) == TRUE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_REGIROCK) { Speed(15); Moves(MOVE_DISARMING_VOICE, MOVE_FLAME_BURST); }; + OPPONENT(SPECIES_WOBBUFFET) { Speed(20); Moves(MOVE_THROAT_CHOP, MOVE_PSYCHIC_FANGS); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_PSYCHIC_FANGS); MOVE(player, MOVE_FLAME_BURST); } + TURN { EXPECT_MOVE(opponent, MOVE_PSYCHIC_FANGS); MOVE(player, MOVE_DISARMING_VOICE); } + TURN { EXPECT_MOVE(opponent, MOVE_PSYCHIC_FANGS); MOVE(player, MOVE_FLAME_BURST);} + } +} + +AI_SINGLE_BATTLE_TEST("AI will select Throat Chop if the sound move is the best damaging move from opposing mon") +{ + GIVEN { + ASSUME(MoveHasMoveEffect(MOVE_THROAT_CHOP, MOVE_EFFECT_THROAT_CHOP) == TRUE); + ASSUME(gBattleMoves[MOVE_PSYCHIC_FANGS].power == 85); + ASSUME(gBattleMoves[MOVE_THROAT_CHOP].power == 80); + ASSUME(gBattleMoves[MOVE_FLAME_BURST].power == 70); + ASSUME(gBattleMoves[MOVE_HYPER_VOICE].power == 90); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_REGIROCK) { Speed(15); Moves(MOVE_HYPER_VOICE, MOVE_FLAME_BURST); }; + OPPONENT(SPECIES_WOBBUFFET) { Speed(20); Moves(MOVE_THROAT_CHOP, MOVE_PSYCHIC_FANGS); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_PSYCHIC_FANGS); MOVE(player, MOVE_FLAME_BURST); } + TURN { EXPECT_MOVE(opponent, MOVE_PSYCHIC_FANGS); MOVE(player, MOVE_HYPER_VOICE); } + TURN { EXPECT_MOVE(opponent, MOVE_THROAT_CHOP); MOVE(player, MOVE_HYPER_VOICE);} + } +}