diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 728e7e8b0483..d252fea400f2 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -3170,6 +3170,8 @@ static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId) { u32 i; bool32 multipleBestMoves = FALSE; + s32 viableMoveScores[MAX_MON_MOVES]; + s32 bestViableMoveScore; s32 noOfHits[MAX_MON_MOVES]; s32 score = 0; s32 leastHits = 1000, leastHitsId = 0; @@ -3186,11 +3188,13 @@ static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId) leastHits = noOfHits[i]; leastHitsId = i; } + viableMoveScores[i] = AI_SCORE_DEFAULT; isPowerfulIgnoredEffect[i] = IsInIgnoredPowerfulMoveEffects(gBattleMoves[moves[i]].effect); } else { noOfHits[i] = -1; + viableMoveScores[i] = 0; isPowerfulIgnoredEffect[i] = FALSE; } /* @@ -3216,19 +3220,45 @@ 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]) - ADJUST_SCORE(3); - else if (CompareMoveAccuracies(battlerAtk, battlerDef, currId, i) == 0) - ADJUST_SCORE(2); - else if (AI_WhichMoveBetter(moves[currId], moves[i], battlerAtk, battlerDef, noOfHits[currId]) == 0) + viableMoveScores[i] -= 3; + else if (!isPowerfulIgnoredEffect[i] && isPowerfulIgnoredEffect[currId]) + viableMoveScores[currId] -= 3; + + switch (CompareMoveAccuracies(battlerAtk, battlerDef, currId, i)) { - // MgbaPrintf_("%S better than %S", gMoveNames[moves[currId]], gMoveNames[moves[i]]); - ADJUST_SCORE(1); + case 0: + viableMoveScores[i] -= 2; + break; + case 1: + viableMoveScores[currId] -= 2; + break; + } + switch (AI_WhichMoveBetter(moves[currId], moves[i], battlerAtk, battlerDef, noOfHits[currId])) + { + case 0: + viableMoveScores[i] -= 1; + break; + case 1: + viableMoveScores[currId] -= 1; + break; } } } // Turns out the current move deals the most dmg compared to the other 3. if (!multipleBestMoves) ADJUST_SCORE(1); + else + { + bestViableMoveScore = 0; + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (viableMoveScores[i] > bestViableMoveScore) + bestViableMoveScore = viableMoveScores[i]; + } + // Unless a better move was found increase score of current move + if (viableMoveScores[currId] == bestViableMoveScore) + ADJUST_SCORE(1); + } } return score; diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index cbda89d66e06..300c15453f1d 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -2083,7 +2083,6 @@ bool32 HasMoveWithSplit(u32 battler, u32 split) if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && GetBattleMoveSplit(moves[i]) == split) return TRUE; } - return FALSE; } diff --git a/test/battle/ai.c b/test/battle/ai.c index 47308df4deb1..1742247584c0 100644 --- a/test/battle/ai.c +++ b/test/battle/ai.c @@ -93,6 +93,9 @@ AI_SINGLE_BATTLE_TEST("AI prefers moves with better accuracy, but only if they b PARAMETRIZE { move1 = MOVE_MEGA_KICK; move2 = MOVE_SLAM; move3 = MOVE_TACKLE; move4 = MOVE_GUST; hp = 5; expectedMove = MOVE_GUST; expectedMove2 = MOVE_TACKLE; turns = 1; } // All moves hit with No guard ability PARAMETRIZE { move1 = MOVE_MEGA_KICK; move2 = MOVE_GUST; hp = 5; expectedMove = MOVE_MEGA_KICK; expectedMove2 = MOVE_GUST; turns = 1; } + // Tests to compare move that always hits and a beneficial effect. A move with higher acc should be chosen in this case. + PARAMETRIZE { move1 = MOVE_SHOCK_WAVE; move2 = MOVE_ICY_WIND; hp = 5; expectedMove = MOVE_SHOCK_WAVE; turns = 1; } + PARAMETRIZE { move1 = MOVE_SHOCK_WAVE; move2 = MOVE_ICY_WIND; move3 = MOVE_THUNDERBOLT; hp = 5; expectedMove = MOVE_SHOCK_WAVE; expectedMove2 = MOVE_THUNDERBOLT; turns = 1; } GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); @@ -105,6 +108,9 @@ AI_SINGLE_BATTLE_TEST("AI prefers moves with better accuracy, but only if they b ASSUME(gBattleMoves[MOVE_MEGA_KICK].accuracy < gBattleMoves[MOVE_STRENGTH].accuracy); ASSUME(gBattleMoves[MOVE_TACKLE].accuracy == 100); ASSUME(gBattleMoves[MOVE_GUST].accuracy == 100); + ASSUME(gBattleMoves[MOVE_SHOCK_WAVE].accuracy == 0); + ASSUME(gBattleMoves[MOVE_THUNDERBOLT].accuracy == 100); + ASSUME(gBattleMoves[MOVE_ICY_WIND].accuracy != 100); OPPONENT(SPECIES_EXPLOUD) { Moves(move1, move2, move3, move4); Ability(abilityAtk); SpAttack(50); } // Low Sp.Atk, so Swift deals less damage than Strength. } WHEN { switch (turns) @@ -192,6 +198,66 @@ AI_SINGLE_BATTLE_TEST("AI prefers Earthquake over Drill Run if both require the } } +AI_SINGLE_BATTLE_TEST("AI prefers a weaker move over a one with a downside effect if both require the same number of hits to ko") +{ + u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE; + u16 hp, expectedMove, turns; + + // Both moves require the same number of turns but Flamethrower will be chosen over Overheat (powerful effect) + PARAMETRIZE { move1 = MOVE_OVERHEAT; move2 = MOVE_FLAMETHROWER; hp = 300; expectedMove = MOVE_FLAMETHROWER; turns = 2; } + // Overheat kill in least amount of turns + PARAMETRIZE { move1 = MOVE_OVERHEAT; move2 = MOVE_FLAMETHROWER; hp = 250; expectedMove = MOVE_OVERHEAT; turns = 1; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { HP(hp); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TYPHLOSION) { Moves(move1, move2); } + } WHEN { + switch (turns) + { + case 1: + TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); } + break; + case 2: + TURN { EXPECT_MOVE(opponent, expectedMove); } + TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); } + break; + } + } + SCENE { + MESSAGE("Wobbuffet fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI prefers moves with the best possible score, chosen randomly if tied") +{ + 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_WOBBUFFET) { Moves(MOVE_THUNDERBOLT, MOVE_SLUDGE_BOMB, MOVE_TAKE_DOWN); } + } WHEN { + TURN { EXPECT_MOVES(opponent, MOVE_THUNDERBOLT, MOVE_SLUDGE_BOMB); SEND_OUT(player, 1); } + } + SCENE { + MESSAGE("Wobbuffet fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI can choose a status move that boosts the attack by two") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { HP(250); }; + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KANGASKHAN) { Moves(MOVE_STRENGTH, MOVE_HORN_ATTACK, MOVE_SWORDS_DANCE); } + } WHEN { + TURN { EXPECT_MOVES(opponent, MOVE_STRENGTH, MOVE_SWORDS_DANCE); } + TURN { EXPECT_MOVE(opponent, MOVE_STRENGTH); SEND_OUT(player, 1); } + } +} + 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;