From f502ba2a696bf9f65093492b910983cea681a4e6 Mon Sep 17 00:00:00 2001 From: sneed <56992013+Sneed69@users.noreply.github.com> Date: Sat, 11 May 2024 20:03:19 +0300 Subject: [PATCH] Stat stage related AI fixes (#4548) * stat stage related AI fixes * add more ai fixes and 2 tests * use legal ability in tests * Fix test and remove mold breaker check * Use DoesBattlerIgnoreAbilityChecks --- include/battle_ai_main.h | 4 +- include/battle_ai_util.h | 1 + src/battle_ai_main.c | 103 +++++++++++++++++++++++++-------------- src/battle_ai_util.c | 64 +++++++++++++++++++++--- test/battle/ai.c | 52 ++++++++++++++++++++ 5 files changed, 179 insertions(+), 45 deletions(-) diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index 18148a89b0..e2c7804091 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -32,13 +32,13 @@ #define BEST_DAMAGE_MOVE 1 // Move with the most amount of hits with the best accuracy/effect #define POWERFUL_STATUS_MOVE 10 // Moves with this score will be chosen over a move that faints target -// Temporary scores that are added together to determine a final score at the at of AI_CalcMoveScore +// Temporary scores that are added together to determine a final score at the at of AI_CalcMoveEffectScore #define WEAK_EFFECT 1 #define DECENT_EFFECT 2 #define GOOD_EFFECT 4 #define BEST_EFFECT 6 -// AI_CalcMoveScore final score +// AI_CalcMoveEffectScore final score #define NOT_GOOD_ENOUGH 0 // Not worth using over a damaging move #define GOOD_MOVE_EFFECTS 2 // Worth using over a damaging move #define PREFERRED_MOVE_EFFECTS 3 // Worth using over a damagin move and is better then DECENT_EFFECT diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index ac7aea429b..bd2b8fb920 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -179,6 +179,7 @@ bool32 SideHasMoveCategory(u32 battlerId, u32 category); // score increases void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score); +void IncreaseStatUpScoreContrary(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score); void IncreasePoisonScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); void IncreaseBurnScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); void IncreaseParalyzeScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 92b2239cd3..429ec6642b 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -3161,7 +3161,7 @@ static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId) return score; } -static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) +static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) { // move data u32 moveEffect = gMovesInfo[move].effect; @@ -3592,7 +3592,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) else ADJUST_SCORE(WEAK_EFFECT); } - else if (aiData->abilities[battlerAtk] != ABILITY_CONTRARY) + else { IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); @@ -4407,56 +4407,88 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) // Consider move effects that target self if (gMovesInfo[move].additionalEffects[i].self) { - u32 oneStageStatId = STAT_CHANGE_ATK + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1; - u32 twoStageStatId = STAT_CHANGE_ATK_2 + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1; + u32 StageStatId; - switch (gMovesInfo[move].additionalEffects[i].moveEffect) + if (aiData->abilities[battlerAtk] != ABILITY_CONTRARY) { - case MOVE_EFFECT_SPD_PLUS_2: - case MOVE_EFFECT_SPD_PLUS_1: - if (aiData->abilities[battlerAtk] != ABILITY_CONTRARY && !AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) - ADJUST_SCORE(GOOD_EFFECT); - break; + switch (gMovesInfo[move].additionalEffects[i].moveEffect) + { case MOVE_EFFECT_ATK_PLUS_1: case MOVE_EFFECT_DEF_PLUS_1: + case MOVE_EFFECT_SPD_PLUS_1: case MOVE_EFFECT_SP_ATK_PLUS_1: case MOVE_EFFECT_SP_DEF_PLUS_1: - case MOVE_EFFECT_ACC_PLUS_1: - case MOVE_EFFECT_EVS_PLUS_1: - IncreaseStatUpScore(battlerAtk, battlerDef, oneStageStatId, &score); + StageStatId = STAT_CHANGE_ATK + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1; + IncreaseStatUpScore(battlerAtk, battlerDef, StageStatId, &score); break; case MOVE_EFFECT_ATK_PLUS_2: case MOVE_EFFECT_DEF_PLUS_2: + case MOVE_EFFECT_SPD_PLUS_2: case MOVE_EFFECT_SP_ATK_PLUS_2: case MOVE_EFFECT_SP_DEF_PLUS_2: - case MOVE_EFFECT_ACC_PLUS_2: - case MOVE_EFFECT_EVS_PLUS_2: - IncreaseStatUpScore(battlerAtk, battlerDef, twoStageStatId, &score); + StageStatId = STAT_CHANGE_ATK_2 + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1; + IncreaseStatUpScore(battlerAtk, battlerDef, StageStatId, &score); break; - // Effects that lower stat(s) - only need to consider Contrary - case MOVE_EFFECT_ATK_MINUS_1: - case MOVE_EFFECT_DEF_MINUS_1: - case MOVE_EFFECT_SPD_MINUS_1: - case MOVE_EFFECT_SP_ATK_MINUS_1: - case MOVE_EFFECT_SP_DEF_MINUS_1: - 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) - 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); - } + case MOVE_EFFECT_ACC_PLUS_1: + case MOVE_EFFECT_ACC_PLUS_2: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ACC, &score); + break; + case MOVE_EFFECT_EVS_PLUS_1: + case MOVE_EFFECT_EVS_PLUS_2: + IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_EVASION, &score); break; case MOVE_EFFECT_RAPID_SPIN: if ((gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY && CountUsablePartyMons(battlerAtk) != 0) || (gStatuses3[battlerAtk] & STATUS3_LEECHSEED || gBattleMons[battlerAtk].status2 & STATUS2_WRAPPED)) ADJUST_SCORE(GOOD_EFFECT); break; + } + } + else + { + switch (gMovesInfo[move].additionalEffects[i].moveEffect) + { + case MOVE_EFFECT_ATK_MINUS_1: + case MOVE_EFFECT_DEF_MINUS_1: + case MOVE_EFFECT_SPD_MINUS_1: + case MOVE_EFFECT_SP_ATK_MINUS_1: + case MOVE_EFFECT_SP_DEF_MINUS_1: + StageStatId = STAT_CHANGE_ATK + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_MINUS_1; + IncreaseStatUpScoreContrary(battlerAtk, battlerDef, StageStatId, &score); + break; + case MOVE_EFFECT_ATK_MINUS_2: + case MOVE_EFFECT_DEF_MINUS_2: + case MOVE_EFFECT_SPD_MINUS_2: + case MOVE_EFFECT_SP_ATK_MINUS_2: + case MOVE_EFFECT_SP_DEF_MINUS_2: + StageStatId = STAT_CHANGE_ATK + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_MINUS_2; + IncreaseStatUpScoreContrary(battlerAtk, battlerDef, StageStatId, &score); + break; + case MOVE_EFFECT_ACC_MINUS_1: + case MOVE_EFFECT_ACC_MINUS_2: + IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_ACC, &score); + break; + case MOVE_EFFECT_EVS_MINUS_1: + case MOVE_EFFECT_EVS_MINUS_2: + IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_EVASION, &score); + break; + case MOVE_EFFECT_DEF_SPDEF_DOWN: + IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); + break; + case MOVE_EFFECT_ATK_DEF_DOWN: + IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score); + IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + break; + case MOVE_EFFECT_SP_ATK_TWO_DOWN: + IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_SPATK_2, &score); + break; + case MOVE_EFFECT_V_CREATE: + IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score); + IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score); + IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score); + break; + } } } else // consider move effects that hinder the target @@ -4639,8 +4671,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score score += AI_CompareDamagingMoves(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex); } - // Calculates score based on effects of a move - score += AI_CalcMoveScore(battlerAtk, battlerDef, move); + score += AI_CalcMoveEffectScore(battlerAtk, battlerDef, move); return score; } diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 4a83fc6e4e..0fcb8b1fc2 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -627,22 +627,35 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3 switch (gMovesInfo[move].additionalEffects[i].moveEffect) { case MOVE_EFFECT_ATK_PLUS_1: + case MOVE_EFFECT_ATK_PLUS_2: if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ATK)) return TRUE; break; - case MOVE_EFFECT_DEF_PLUS_2: case MOVE_EFFECT_DEF_PLUS_1: + case MOVE_EFFECT_DEF_PLUS_2: if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_DEF)) return TRUE; break; case MOVE_EFFECT_SPD_PLUS_1: + case MOVE_EFFECT_SPD_PLUS_2: if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPEED)) return TRUE; break; case MOVE_EFFECT_SP_ATK_PLUS_1: + case MOVE_EFFECT_SP_ATK_PLUS_2: if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPATK)) return TRUE; break; + case MOVE_EFFECT_EVS_PLUS_1: + case MOVE_EFFECT_EVS_PLUS_2: + if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_EVASION)) + return TRUE; + break; + case MOVE_EFFECT_ACC_PLUS_1: + case MOVE_EFFECT_ACC_PLUS_2: + if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ACC)) + return TRUE; + break; case MOVE_EFFECT_ALL_STATS_UP: for (i = STAT_ATK; i <= NUM_STATS; i++) { @@ -737,18 +750,45 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s case MOVE_EFFECT_DEF_MINUS_1: case MOVE_EFFECT_SPD_MINUS_1: case MOVE_EFFECT_SP_ATK_MINUS_1: + case MOVE_EFFECT_SP_DEF_MINUS_1: + case MOVE_EFFECT_EVS_MINUS_1: + case MOVE_EFFECT_ACC_MINUS_1: + case MOVE_EFFECT_ATK_MINUS_2: + case MOVE_EFFECT_DEF_MINUS_2: + case MOVE_EFFECT_SPD_MINUS_2: + case MOVE_EFFECT_SP_ATK_MINUS_2: + case MOVE_EFFECT_SP_DEF_MINUS_2: + case MOVE_EFFECT_EVS_MINUS_2: + case MOVE_EFFECT_ACC_MINUS_2: case MOVE_EFFECT_SP_ATK_TWO_DOWN: case MOVE_EFFECT_V_CREATE: case MOVE_EFFECT_ATK_DEF_DOWN: case MOVE_EFFECT_DEF_SPDEF_DOWN: - case MOVE_EFFECT_SP_DEF_MINUS_1: - case MOVE_EFFECT_SP_DEF_MINUS_2: - if ((gMovesInfo[move].additionalEffects[i].self && GetBattlerAbility(battlerAtk) != ABILITY_CONTRARY) - || (noOfHitsToKo != 1 && abilityDef == ABILITY_CONTRARY && !IsMoldBreakerTypeAbility(abilityAtk))) + if ((gMovesInfo[move].additionalEffects[i].self && abilityAtk != ABILITY_CONTRARY) + || (noOfHitsToKo != 1 && abilityDef == ABILITY_CONTRARY && !DoesBattlerIgnoreAbilityChecks(abilityAtk, move))) return TRUE; break; case MOVE_EFFECT_RECHARGE: return gMovesInfo[move].additionalEffects[i].self; + case MOVE_EFFECT_ATK_PLUS_1: + case MOVE_EFFECT_DEF_PLUS_1: + case MOVE_EFFECT_SPD_PLUS_1: + case MOVE_EFFECT_SP_ATK_PLUS_1: + case MOVE_EFFECT_SP_DEF_PLUS_1: + case MOVE_EFFECT_EVS_PLUS_1: + case MOVE_EFFECT_ACC_PLUS_1: + case MOVE_EFFECT_ATK_PLUS_2: + case MOVE_EFFECT_DEF_PLUS_2: + case MOVE_EFFECT_SPD_PLUS_2: + case MOVE_EFFECT_SP_ATK_PLUS_2: + case MOVE_EFFECT_SP_DEF_PLUS_2: + case MOVE_EFFECT_EVS_PLUS_2: + case MOVE_EFFECT_ACC_PLUS_2: + case MOVE_EFFECT_ALL_STATS_UP: + if ((gMovesInfo[move].additionalEffects[i].self && abilityAtk == ABILITY_CONTRARY) + || (noOfHitsToKo != 1 && !(abilityDef == ABILITY_CONTRARY && !DoesBattlerIgnoreAbilityChecks(abilityAtk, move)))) + return TRUE; + break; } } break; @@ -3388,13 +3428,13 @@ bool32 IsRecycleEncouragedItem(u32 item) return FALSE; } -void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score) +static void IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score, bool32 considerContrary) { 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) + if (considerContrary && AI_DATA->abilities[battlerAtk] == ABILITY_CONTRARY) return; // Don't increase stat if AI is at +4 @@ -3488,6 +3528,16 @@ void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score) } } +void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score) +{ + IncreaseStatUpScoreInternal(battlerAtk, battlerDef, statId, score, TRUE); +} + +void IncreaseStatUpScoreContrary(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score) +{ + IncreaseStatUpScoreInternal(battlerAtk, battlerDef, statId, score, FALSE); +} + void IncreasePoisonScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) { if (((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) diff --git a/test/battle/ai.c b/test/battle/ai.c index 0d890da466..11702a0c5a 100644 --- a/test/battle/ai.c +++ b/test/battle/ai.c @@ -830,3 +830,55 @@ AI_SINGLE_BATTLE_TEST("AI will choose Surf over Thunderbolt and Ice Beam if the TURN { EXPECT_MOVE(opponent, MOVE_SURF); } } } + +AI_SINGLE_BATTLE_TEST("AI will choose Scratch over Power-up Punch with Contrary") +{ + u32 ability; + + PARAMETRIZE {ability = ABILITY_SUCTION_CUPS; } + PARAMETRIZE {ability = ABILITY_CONTRARY; } + GIVEN { + ASSUME(gMovesInfo[MOVE_SCRATCH].power == 40); + ASSUME(gMovesInfo[MOVE_SCRATCH].type == TYPE_NORMAL); + ASSUME(gMovesInfo[MOVE_POWER_UP_PUNCH].power == 40); + ASSUME(gMovesInfo[MOVE_POWER_UP_PUNCH].type == TYPE_FIGHTING); + ASSUME(gSpeciesInfo[SPECIES_SQUIRTLE].types[0] == TYPE_WATER); + ASSUME(gSpeciesInfo[SPECIES_SQUIRTLE].types[1] == TYPE_WATER); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_SQUIRTLE) { }; + OPPONENT(SPECIES_MALAMAR) { Ability(ability); Moves(MOVE_SCRATCH, MOVE_POWER_UP_PUNCH); } + } WHEN { + TURN { + if (ability != ABILITY_CONTRARY) + EXPECT_MOVE(opponent, MOVE_POWER_UP_PUNCH); + else + EXPECT_MOVE(opponent, MOVE_SCRATCH); + } + } +} + +AI_SINGLE_BATTLE_TEST("AI will choose Superpower over Outrage with Contrary") +{ + u32 ability; + + PARAMETRIZE {ability = ABILITY_SUCTION_CUPS; } + PARAMETRIZE {ability = ABILITY_CONTRARY; } + GIVEN { + ASSUME(gMovesInfo[MOVE_SUPERPOWER].power == 120); + ASSUME(gMovesInfo[MOVE_SUPERPOWER].type == TYPE_FIGHTING); + ASSUME(gMovesInfo[MOVE_OUTRAGE].power == 120); + ASSUME(gMovesInfo[MOVE_OUTRAGE].type == TYPE_DRAGON); + ASSUME(gSpeciesInfo[SPECIES_SQUIRTLE].types[0] == TYPE_WATER); + ASSUME(gSpeciesInfo[SPECIES_SQUIRTLE].types[1] == TYPE_WATER); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_SQUIRTLE) { }; + OPPONENT(SPECIES_MALAMAR) { Ability(ability); Moves(MOVE_OUTRAGE, MOVE_SUPERPOWER); } + } WHEN { + TURN { + if (ability != ABILITY_CONTRARY) + EXPECT_MOVE(opponent, MOVE_OUTRAGE); + else + EXPECT_MOVE(opponent, MOVE_SUPERPOWER); + } + } +}