diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 3d37a185c730..2e0629b0cd68 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -91,7 +91,6 @@ bool32 AI_IsDamagedByRecoil(u32 battler); u32 GetNoOfHitsToKO(u32 dmg, s32 hp); u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef); u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex); -bool32 IsInIgnoredPowerfulMoveEffects(u32 effect); void SetMovesDamageResults(u32 battlerAtk, u16 *moves); u32 GetMoveDamageResult(u32 battlerAtk, u32 battlerDef, u32 moveIndex); u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef); @@ -113,6 +112,7 @@ bool32 IsMoveRedirectionPrevented(u32 move, u32 atkAbility); bool32 IsMoveEncouragedToHit(u32 battlerAtk, u32 battlerDef, u32 move); bool32 IsHazardMoveEffect(u32 moveEffect); bool32 IsEncoreEncouragedEffect(u32 moveEffect); +bool32 IsChargingMove(u32 battlerAtk, u32 effect); void ProtectChecks(u32 battlerAtk, u32 battlerDef, u32 move, u32 predictedMove, s32 *score); bool32 ShouldSetSandstorm(u32 battler, u32 ability, u32 holdEffect); bool32 ShouldSetHail(u32 battler, u32 ability, u32 holdEffect); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 8339195ea728..33282b955c6e 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -760,6 +760,9 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) RETURN_SCORE_MINUS(20); // if target off screen and we go first, don't use move + if (IsChargingMove(battlerAtk, moveEffect) && CanTargetFaintAi(battlerDef, battlerAtk)) + RETURN_SCORE_MINUS(10); + // check if negates type switch (effectiveness) { @@ -1356,22 +1359,13 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; //case EFFECT_BIDE: - //case EFFECT_SUPER_FANG: //case EFFECT_RECHARGE: - case EFFECT_LEVEL_DAMAGE: - case EFFECT_PSYWAVE: //case EFFECT_COUNTER: - //case EFFECT_FLAIL: - case EFFECT_RETURN: case EFFECT_PRESENT: - case EFFECT_FRUSTRATION: case EFFECT_SONICBOOM: //case EFFECT_MIRROR_COAT: - case EFFECT_SKULL_BASH: case EFFECT_FOCUS_PUNCH: - case EFFECT_SUPERPOWER: //case EFFECT_ENDEAVOR: - case EFFECT_LOW_KICK: // AI_CBM_HighRiskForDamage if (aiData->abilities[battlerDef] == ABILITY_WONDER_GUARD && effectiveness < AI_EFFECTIVENESS_x2) ADJUST_SCORE(-10); @@ -1906,17 +1900,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) || (gBattleMons[battlerDef].status2 & (STATUS2_TRANSFORMED | STATUS2_SUBSTITUTE))) //Leave out Illusion b/c AI is supposed to be fooled ADJUST_SCORE(-10); break; - case EFFECT_TWO_TURNS_ATTACK: - if (aiData->holdEffects[battlerAtk] != HOLD_EFFECT_POWER_HERB && CanTargetFaintAi(battlerDef, battlerAtk)) - ADJUST_SCORE(-6); - break; - case EFFECT_RECHARGE: - if (aiData->abilities[battlerDef] == ABILITY_WONDER_GUARD && effectiveness < AI_EFFECTIVENESS_x2) - ADJUST_SCORE(-10); - else if (aiData->abilities[battlerAtk] != ABILITY_TRUANT - && !CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) - ADJUST_SCORE(-2); - break; case EFFECT_SPITE: case EFFECT_MIMIC: if (AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) // Attacker should go first @@ -2109,13 +2092,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_SPECTRAL_THIEF: break; - case EFFECT_SOLAR_BEAM: - if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_POWER_HERB - || ((AI_GetWeather(aiData) & B_WEATHER_SUN) && aiData->holdEffects[battlerAtk] != HOLD_EFFECT_UTILITY_UMBRELLA)) - break; - if (CanTargetFaintAi(battlerDef, battlerAtk)) //Attacker can be knocked out - ADJUST_SCORE(-4); - break; case EFFECT_SEMI_INVULNERABLE: if (predictedMove != MOVE_NONE && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER @@ -2674,6 +2650,10 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) && !BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) ADJUST_SCORE(-10); break; + case EFFECT_LOW_KICK: + if (IsDynamaxed(battlerDef)) + ADJUST_SCORE(-10); + break; case EFFECT_PLACEHOLDER: return 0; // cannot even select } // move effect checks @@ -3151,7 +3131,7 @@ static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId) s32 score = 0; s32 leastHits = 1000; u16 *moves = GetMovesArray(battlerAtk); - bool8 isPowerfulIgnoredEffect[MAX_MON_MOVES]; + bool8 isChargingMoveEffect[MAX_MON_MOVES]; for (i = 0; i < MAX_MON_MOVES; i++) { @@ -3163,13 +3143,13 @@ static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId) leastHits = noOfHits[i]; } viableMoveScores[i] = AI_SCORE_DEFAULT; - isPowerfulIgnoredEffect[i] = IsInIgnoredPowerfulMoveEffects(gBattleMoves[moves[i]].effect); + isChargingMoveEffect[i] = IsChargingMove(battlerAtk, gBattleMoves[moves[i]].effect); } else { noOfHits[i] = -1; viableMoveScores[i] = 0; - isPowerfulIgnoredEffect[i] = FALSE; + isChargingMoveEffect[i] = FALSE; } /* MgbaPrintf_("%S: required hits: %d Dmg: %d", gMoveNames[moves[i]], noOfHits[i], AI_DATA->simulatedDmg[battlerAtk][battlerDef][i]); @@ -3178,7 +3158,7 @@ static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId) // Priority list: // 1. Less no of hits to ko - // 2. Not in the powerful but ignored move effects table + // 2. Not charging // 3. More accuracy // 4. Better effect @@ -3193,9 +3173,9 @@ static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId) { multipleBestMoves = TRUE; // We need to make sure it's the current move which is objectively better. - if (isPowerfulIgnoredEffect[i] && !isPowerfulIgnoredEffect[currId]) + if (isChargingMoveEffect[i] && !isChargingMoveEffect[currId]) viableMoveScores[i] -= 3; - else if (!isPowerfulIgnoredEffect[i] && isPowerfulIgnoredEffect[currId]) + else if (!isChargingMoveEffect[i] && isChargingMoveEffect[currId]) viableMoveScores[currId] -= 3; switch (CompareMoveAccuracies(battlerAtk, battlerDef, currId, i)) @@ -4847,15 +4827,6 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score IncreasePoisonScore(battlerAtk, battlerDef, move, &score); IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPEED, &score); break; - case EFFECT_SOLAR_BEAM: - if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, movesetIndex) >= 2 - && HasMoveEffect(battlerAtk, EFFECT_SUNNY_DAY) && (AI_GetWeather(aiData) & B_WEATHER_SUN)) // Use Sunny Day to boost damage. - ADJUST_SCORE(-3); - case EFFECT_TWO_TURNS_ATTACK: - case EFFECT_SKULL_BASH: - if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_POWER_HERB) - ADJUST_SCORE(2); - break; case EFFECT_COUNTER: if (!IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) && predictedMove != MOVE_NONE) { @@ -5246,9 +5217,6 @@ static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_BELLY_DRUM: case EFFECT_PSYCH_UP: case EFFECT_MIRROR_COAT: - case EFFECT_SOLAR_BEAM: - case EFFECT_TWO_TURNS_ATTACK: - case EFFECT_ERUPTION: case EFFECT_TICKLE: case EFFECT_SUNNY_DAY: case EFFECT_SANDSTORM: diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index dd06a69eaf00..68450e4e5998 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -363,25 +363,6 @@ static const u16 sEncouragedEncoreEffects[] = EFFECT_CAMOUFLAGE, }; -// For the purposes of determining the most powerful move in a moveset, these -// moves are treated the same as having a power of 0 or 1 -#define IGNORED_MOVES_END 0xFFFF -static const u16 sIgnoredPowerfulMoveEffects[] = -{ - EFFECT_EXPLOSION, - EFFECT_DREAM_EATER, - EFFECT_RECHARGE, - EFFECT_SKULL_BASH, - EFFECT_SOLAR_BEAM, - EFFECT_FOCUS_PUNCH, - EFFECT_SUPERPOWER, - EFFECT_ERUPTION, - EFFECT_OVERHEAT, - EFFECT_MIND_BLOWN, - EFFECT_MAKE_IT_RAIN, - IGNORED_MOVES_END -}; - // Functions u32 GetAIChosenMove(u32 battlerId) { @@ -980,6 +961,11 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s switch (gBattleMoves[move].effect) { case EFFECT_RECHARGE: + case EFFECT_SUPERPOWER: + case EFFECT_OVERHEAT: + case EFFECT_MAKE_IT_RAIN: + case EFFECT_MIND_BLOWN: + case EFFECT_STEEL_BEAM: return TRUE; case EFFECT_RECOIL_25: case EFFECT_RECOIL_IF_MISS: @@ -1054,22 +1040,6 @@ u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex) return GetNoOfHitsToKOBattlerDmg(AI_DATA->simulatedDmg[battlerAtk][battlerDef][moveIndex], battlerDef); } -bool32 IsInIgnoredPowerfulMoveEffects(u32 effect) -{ - u32 i; - for (i = 0; sIgnoredPowerfulMoveEffects[i] != IGNORED_MOVES_END; i++) - { - if (effect == sIgnoredPowerfulMoveEffects[i]) - { - // Don't ingore Solar Beam if doesn't have a charging turn. - if (effect == EFFECT_SOLAR_BEAM && (AI_GetWeather(AI_DATA) & B_WEATHER_SUN)) - break; - return TRUE; - } - } - return FALSE; -} - void SetMovesDamageResults(u32 battlerAtk, u16 *moves) { s32 i, j, battlerDef, bestId, currId, hp, result; @@ -1079,7 +1049,7 @@ void SetMovesDamageResults(u32 battlerAtk, u16 *moves) for (i = 0; i < MAX_MON_MOVES; i++) { u32 move = moves[i]; - if (move == MOVE_NONE || move == MOVE_UNAVAILABLE || gBattleMoves[move].power == 0 || IsInIgnoredPowerfulMoveEffects(gBattleMoves[move].effect)) + if (move == MOVE_NONE || move == MOVE_UNAVAILABLE || gBattleMoves[move].power == 0) isNotConsidered[i] = TRUE; else isNotConsidered[i] = FALSE; @@ -1094,11 +1064,10 @@ void SetMovesDamageResults(u32 battlerAtk, u16 *moves) if (isNotConsidered[i]) { - result = MOVE_POWER_OTHER; // Move has a power of 0/1, or is in the group sIgnoredPowerfulMoveEffects + result = MOVE_POWER_OTHER; // Move has a power of 0/1 } else { - // Considered move has power and is not in sIgnoredPowerfulMoveEffects // Check all other moves and calculate their power for (j = 0; j < MAX_MON_MOVES; j++) { @@ -2394,6 +2363,24 @@ bool32 IsEncoreEncouragedEffect(u32 moveEffect) return FALSE; } +bool32 IsChargingMove(u32 battlerAtk, u32 effect) +{ + switch (effect) + { + case EFFECT_SOLAR_BEAM: + if (AI_GetWeather(AI_DATA) & B_WEATHER_SUN) + return FALSE; + case EFFECT_SKULL_BASH: + case EFFECT_METEOR_BEAM: + case EFFECT_TWO_TURNS_ATTACK: + if (AI_DATA->holdEffects[battlerAtk] == HOLD_EFFECT_POWER_HERB) + return FALSE; + return TRUE; + default: + return FALSE; + } +} + static u32 GetLeechSeedDamage(u32 battlerId) { u32 damage = 0; diff --git a/test/battle/ai.c b/test/battle/ai.c index 6f863e3f311f..91a9bc663ae6 100644 --- a/test/battle/ai.c +++ b/test/battle/ai.c @@ -260,8 +260,8 @@ AI_SINGLE_BATTLE_TEST("AI can choose a status move that boosts the attack by two AI_SINGLE_BATTLE_TEST("AI chooses the safest option to faint the target, taking into account accuracy and move effect") { u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE; - u16 expectedMove, abilityAtk = ABILITY_NONE; - u16 expectedMove2 = MOVE_NONE; + u16 expectedMove, expectedMove2 = MOVE_NONE; + u16 abilityAtk = ABILITY_NONE, holdItemAtk = ITEM_NONE; // Psychic is not very effective, but always hits. Solarbeam requires a charging turn, Double Edge has recoil and Focus Blast can miss; PARAMETRIZE { abilityAtk = ABILITY_STURDY; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE; expectedMove = MOVE_PSYCHIC; } @@ -272,12 +272,24 @@ AI_SINGLE_BATTLE_TEST("AI chooses the safest option to faint the target, taking // This time it's Solarbeam + Psychic, because the weather is sunny. PARAMETRIZE { abilityAtk = ABILITY_DROUGHT; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE; expectedMove = MOVE_PSYCHIC; expectedMove2 = MOVE_SOLAR_BEAM; } + // Psychic and Solar Beam are chosen because user is holding Power Herb + PARAMETRIZE { abilityAtk = ABILITY_STURDY; holdItemAtk = ITEM_POWER_HERB; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE; + expectedMove = MOVE_PSYCHIC; expectedMove2 = MOVE_SOLAR_BEAM; } + // Psychic and Skull Bash are chosen because user is holding Power Herb + PARAMETRIZE { abilityAtk = ABILITY_STURDY; holdItemAtk = ITEM_POWER_HERB; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SKULL_BASH; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE; + expectedMove = MOVE_PSYCHIC; expectedMove2 = MOVE_SKULL_BASH; } + // Skull Bash is chosen because it's the most accurate and is holding Power Herb + PARAMETRIZE { abilityAtk = ABILITY_STURDY; holdItemAtk = ITEM_POWER_HERB; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SKULL_BASH; move3 = MOVE_SLAM; move4 = MOVE_DOUBLE_EDGE; + expectedMove = MOVE_SKULL_BASH; } + // Crabhammer is chosen even if Skull Bash is more accurate, the user has no Power Herb + PARAMETRIZE { abilityAtk = ABILITY_STURDY; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SKULL_BASH; move3 = MOVE_SLAM; move4 = MOVE_CRABHAMMER; + expectedMove = MOVE_CRABHAMMER; } GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET) { HP(5); } PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_GEODUDE) { Moves(move1, move2, move3, move4); Ability(abilityAtk); } + OPPONENT(SPECIES_GEODUDE) { Moves(move1, move2, move3, move4); Ability(abilityAtk); Item(holdItemAtk); } } WHEN { TURN { if (expectedMove2 == MOVE_NONE) { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); } else {EXPECT_MOVES(opponent, expectedMove, expectedMove2); SCORE_EQ(opponent, expectedMove, expectedMove2); SEND_OUT(player, 1);} @@ -288,6 +300,36 @@ AI_SINGLE_BATTLE_TEST("AI chooses the safest option to faint the target, taking } } +AI_SINGLE_BATTLE_TEST("AI won't use Solar Beam if there is no Sun up or the user is not holding Power Herb") +{ + u16 abilityAtk = ABILITY_NONE; + u16 holdItemAtk = ITEM_NONE; + + PARAMETRIZE { abilityAtk = ABILITY_DROUGHT; } + PARAMETRIZE { holdItemAtk = ITEM_POWER_HERB; } + PARAMETRIZE { } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { HP(211); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TYPHLOSION) { Moves(MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); Ability(abilityAtk); Item(holdItemAtk); } + } WHEN { + if (abilityAtk == ABILITY_DROUGHT) { + TURN { EXPECT_MOVES(opponent, MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); } + TURN { EXPECT_MOVES(opponent, MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); SEND_OUT(player, 1); } + } else if (holdItemAtk == ITEM_POWER_HERB) { + TURN { EXPECT_MOVES(opponent, MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); MOVE(player, MOVE_KNOCK_OFF); } + TURN { EXPECT_MOVE(opponent, MOVE_GRASS_PLEDGE); SEND_OUT(player, 1); } + } else { + TURN { EXPECT_MOVE(opponent, MOVE_GRASS_PLEDGE); } + TURN { EXPECT_MOVE(opponent, MOVE_GRASS_PLEDGE); SEND_OUT(player, 1); } + } + } SCENE { + MESSAGE("Wobbuffet fainted!"); + } +} + AI_SINGLE_BATTLE_TEST("AI won't use ground type attacks against flying type Pokemon unless Gravity is in effect") { GIVEN {