From e79583be63f5a7182ae050b54560aced23a9804c Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Thu, 9 Nov 2023 20:53:14 +0100 Subject: [PATCH] Remove function GetMoveDamageResult (#3536) * Remove function GetMoveDamageResult * fix test * test clean up --------- Co-authored-by: DizzyEggg --- include/battle.h | 1 - include/battle_ai_util.h | 2 - include/constants/battle_ai.h | 6 - src/battle_ai_main.c | 239 +++++++++++++++---------------- src/battle_ai_util.c | 84 ----------- test/battle/ai.c | 5 +- test/battle/ai_check_viability.c | 24 +++- test/battle/ai_trytofaint.c | 48 +++++++ 8 files changed, 186 insertions(+), 223 deletions(-) create mode 100644 test/battle/ai_trytofaint.c diff --git a/include/battle.h b/include/battle.h index 350a313d93..e448fb7720 100644 --- a/include/battle.h +++ b/include/battle.h @@ -301,7 +301,6 @@ struct AiLogicData u8 hpPercents[MAX_BATTLERS_COUNT]; u16 partnerMove; u16 speedStats[MAX_BATTLERS_COUNT]; // Speed stats for all battles, calculated only once, same way as damages - u8 moveDmgResult[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // MOVE_POWER defines for GetMoveDamageResult ; attacker, target, moveIndex s32 simulatedDmg[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex u8 effectiveness[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex u8 moveAccuracy[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 2e0629b0cd..99742a607c 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -91,8 +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); -void SetMovesDamageResults(u32 battlerAtk, u16 *moves); -u32 GetMoveDamageResult(u32 battlerAtk, u32 battlerDef, u32 moveIndex); u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef); uq4_12_t AI_GetTypeEffectiveness(u32 move, u32 battlerAtk, u32 battlerDef); u32 AI_GetMoveEffectiveness(u32 move, u32 battlerAtk, u32 battlerDef); diff --git a/include/constants/battle_ai.h b/include/constants/battle_ai.h index 39d0640de1..7ba3e6c506 100644 --- a/include/constants/battle_ai.h +++ b/include/constants/battle_ai.h @@ -24,12 +24,6 @@ #define AI_EFFECTIVENESS_x0_125 1 #define AI_EFFECTIVENESS_x0 0 -// get_how_powerful_move_is -#define MOVE_POWER_OTHER 0 -#define MOVE_POWER_BEST 1 -#define MOVE_POWER_GOOD 2 // Similar dmg range with best. -#define MOVE_POWER_WEAK 3 // Significantly lower than best and good. - // AI Flags. Most run specific functions to update score, new flags are used for internal logic in other scripts #define AI_FLAG_CHECK_BAD_MOVE (1 << 0) #define AI_FLAG_TRY_TO_FAINT (1 << 1) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 33282b955c..38cbff57cb 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -388,7 +388,6 @@ static void SetBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u3 aiData->effectiveness[battlerAtk][battlerDef][i] = effectiveness; } } - SetMovesDamageResults(battlerAtk, moves); } void SetAiLogicDataForTurn(struct AiLogicData *aiData) @@ -2667,36 +2666,25 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { u32 movesetIndex = AI_THINKING_STRUCT->movesetIndex; - bool32 aiFaster; if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) return score; if (gBattleMoves[move].power == 0) - return score; // can't make anything faint with no power + return score; // can't make anything faint with no power - aiFaster = AI_STRIKES_FIRST(battlerAtk, battlerDef, move); if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, 0) && gBattleMoves[move].effect != EFFECT_EXPLOSION) { - // this move can faint the target - if (aiFaster || GetMovePriority(battlerAtk, move) > 0) - ADJUST_SCORE(5); // we go first or we're using priority move + if (AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) + ADJUST_SCORE(5); // Move hits first and can faint the target else - ADJUST_SCORE(4); + ADJUST_SCORE(4); // Faints target but slower } - else + else if (CanTargetFaintAi(battlerDef, battlerAtk) + && GetWhichBattlerFaster(battlerAtk, battlerDef, TRUE) != AI_IS_FASTER + && GetMovePriority(battlerAtk, move) > 0) { - if (GetMoveDamageResult(battlerAtk, battlerDef, movesetIndex) == MOVE_POWER_OTHER) - ADJUST_SCORE(-1); - } - - //AI_TryToFaint_CheckIfDanger - if (!aiFaster && CanTargetFaintAi(battlerDef, battlerAtk)) - { // AI_TryToFaint_Danger - if (GetMoveDamageResult(battlerAtk, battlerDef, movesetIndex) != MOVE_POWER_BEST) - ADJUST_SCORE(-1); - else - ADJUST_SCORE(1); + ADJUST_SCORE(2); // If slower and target can kill } return score; @@ -2826,113 +2814,110 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) // check specific target if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) { - if (GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == MOVE_POWER_OTHER) + // partner ability checks + if (!partnerProtecting && moveTarget != MOVE_TARGET_BOTH && !DoesBattlerIgnoreAbilityChecks(aiData->abilities[battlerAtk], move)) { - // partner ability checks - if (!partnerProtecting && moveTarget != MOVE_TARGET_BOTH && !DoesBattlerIgnoreAbilityChecks(aiData->abilities[battlerAtk], move)) + switch (atkPartnerAbility) { - switch (atkPartnerAbility) + case ABILITY_VOLT_ABSORB: + if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_HP_AWARE)) { - case ABILITY_VOLT_ABSORB: - if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_HP_AWARE)) - { - RETURN_SCORE_MINUS(10); - } - break; // handled in AI_HPAware - case ABILITY_MOTOR_DRIVE: - if (moveType == TYPE_ELECTRIC && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPEED)) - { - RETURN_SCORE_PLUS(1); - } - break; - case ABILITY_LIGHTNING_ROD: - if (moveType == TYPE_ELECTRIC - && HasMoveWithSplit(battlerAtkPartner, SPLIT_SPECIAL) - && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPATK)) - { - RETURN_SCORE_PLUS(1); - } - break; - case ABILITY_WATER_ABSORB: - case ABILITY_DRY_SKIN: - if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_HP_AWARE)) - { - RETURN_SCORE_MINUS(10); - } - break; // handled in AI_HPAware - case ABILITY_STORM_DRAIN: - if (moveType == TYPE_WATER - && HasMoveWithSplit(battlerAtkPartner, SPLIT_SPECIAL) - && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPATK)) - { - RETURN_SCORE_PLUS(1); - } - break; - case ABILITY_WATER_COMPACTION: - if (moveType == TYPE_WATER && GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == MOVE_POWER_WEAK) - { - RETURN_SCORE_PLUS(1); // only mon with this ability is weak to water so only make it okay if we do very little damage - } RETURN_SCORE_MINUS(10); - break; - case ABILITY_FLASH_FIRE: - if (moveType == TYPE_FIRE - && HasMoveWithType(battlerAtkPartner, TYPE_FIRE) - && !(gBattleResources->flags->flags[battlerAtkPartner] & RESOURCE_FLAG_FLASH_FIRE)) - { - RETURN_SCORE_PLUS(1); - } - break; - case ABILITY_SAP_SIPPER: - if (moveType == TYPE_GRASS - && HasMoveWithSplit(battlerAtkPartner, SPLIT_PHYSICAL) - && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)) - { - RETURN_SCORE_PLUS(1); - } - break; - case ABILITY_JUSTIFIED: - if (moveType == TYPE_DARK - && !IS_MOVE_STATUS(move) - && HasMoveWithSplit(battlerAtkPartner, SPLIT_PHYSICAL) - && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK) - && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 1)) - { - RETURN_SCORE_PLUS(1); - } - break; - case ABILITY_RATTLED: - if (!IS_MOVE_STATUS(move) - && (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG) - && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPEED) - && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 1)) - { - RETURN_SCORE_PLUS(1); - } - break; - case ABILITY_CONTRARY: - if (IsStatLoweringEffect(effect)) - { - RETURN_SCORE_PLUS(2); - } - break; - case ABILITY_DEFIANT: - if (IsStatLoweringEffect(effect) - && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)) - { - RETURN_SCORE_PLUS(1); - } - break; - case ABILITY_COMPETITIVE: - if (IsStatLoweringEffect(effect) - && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPATK)) - { - RETURN_SCORE_PLUS(1); - } - break; } - } // ability checks - } // move power check + break; // handled in AI_HPAware + case ABILITY_MOTOR_DRIVE: + if (moveType == TYPE_ELECTRIC && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPEED)) + { + RETURN_SCORE_PLUS(1); + } + break; + case ABILITY_LIGHTNING_ROD: + if (moveType == TYPE_ELECTRIC + && HasMoveWithSplit(battlerAtkPartner, SPLIT_SPECIAL) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPATK)) + { + RETURN_SCORE_PLUS(1); + } + break; + case ABILITY_WATER_ABSORB: + case ABILITY_DRY_SKIN: + if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_HP_AWARE)) + { + RETURN_SCORE_MINUS(10); + } + break; // handled in AI_HPAware + case ABILITY_STORM_DRAIN: + if (moveType == TYPE_WATER + && HasMoveWithSplit(battlerAtkPartner, SPLIT_SPECIAL) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPATK)) + { + RETURN_SCORE_PLUS(1); + } + break; + case ABILITY_WATER_COMPACTION: + if (moveType == TYPE_WATER && GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) >= 4) + { + RETURN_SCORE_PLUS(1); // only mon with this ability is weak to water so only make it okay if we do very little damage + } + RETURN_SCORE_MINUS(10); + break; + case ABILITY_FLASH_FIRE: + if (moveType == TYPE_FIRE + && HasMoveWithType(battlerAtkPartner, TYPE_FIRE) + && !(gBattleResources->flags->flags[battlerAtkPartner] & RESOURCE_FLAG_FLASH_FIRE)) + { + RETURN_SCORE_PLUS(1); + } + break; + case ABILITY_SAP_SIPPER: + if (moveType == TYPE_GRASS + && HasMoveWithSplit(battlerAtkPartner, SPLIT_PHYSICAL) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)) + { + RETURN_SCORE_PLUS(1); + } + break; + case ABILITY_JUSTIFIED: + if (moveType == TYPE_DARK + && !IS_MOVE_STATUS(move) + && HasMoveWithSplit(battlerAtkPartner, SPLIT_PHYSICAL) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK) + && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 1)) + { + RETURN_SCORE_PLUS(1); + } + break; + case ABILITY_RATTLED: + if (!IS_MOVE_STATUS(move) + && (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPEED) + && !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, AI_THINKING_STRUCT->movesetIndex, 1)) + { + RETURN_SCORE_PLUS(1); + } + break; + case ABILITY_CONTRARY: + if (IsStatLoweringEffect(effect)) + { + RETURN_SCORE_PLUS(2); + } + break; + case ABILITY_DEFIANT: + if (IsStatLoweringEffect(effect) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)) + { + RETURN_SCORE_PLUS(1); + } + break; + case ABILITY_COMPETITIVE: + if (IsStatLoweringEffect(effect) + && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPATK)) + { + RETURN_SCORE_PLUS(1); + } + break; + } + } // ability checks // attacker move effects specifically targeting partner if (!partnerProtecting) @@ -4832,7 +4817,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score { if (gDisableStructs[battlerDef].tauntTimer != 0) ADJUST_SCORE(1); // target must use damaging move - if (GetMoveDamageResult(battlerDef, battlerAtk, predictedMoveSlot) >= MOVE_POWER_GOOD && GetBattleMoveSplit(predictedMove) == SPLIT_PHYSICAL) + if (GetNoOfHitsToKOBattler(battlerDef, battlerAtk, predictedMoveSlot) >= 2 && GetBattleMoveSplit(predictedMove) == SPLIT_PHYSICAL) ADJUST_SCORE(3); } break; @@ -4841,7 +4826,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score { if (gDisableStructs[battlerDef].tauntTimer != 0) ADJUST_SCORE(1); // target must use damaging move - if (GetMoveDamageResult(battlerDef, battlerAtk, predictedMoveSlot) >= MOVE_POWER_GOOD && GetBattleMoveSplit(predictedMove) == SPLIT_SPECIAL) + if (GetNoOfHitsToKOBattler(battlerDef, battlerAtk, predictedMoveSlot) >= 2 && GetBattleMoveSplit(predictedMove) == SPLIT_SPECIAL) ADJUST_SCORE(3); } break; @@ -5056,7 +5041,9 @@ static s32 AI_PreferStrongestMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) return score; - if (GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == MOVE_POWER_BEST) + if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == 1) + ADJUST_SCORE(3); + else if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == 2) ADJUST_SCORE(2); return score; @@ -5069,7 +5056,7 @@ static s32 AI_PreferBatonPass(u32 battlerAtk, u32 battlerDef, u32 move, s32 scor if (IS_TARGETING_PARTNER(battlerAtk, battlerDef) || CountUsablePartyMons(battlerAtk) == 0 - || GetMoveDamageResult(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) != MOVE_POWER_OTHER + || gBattleMoves[move].power != 0 || !HasMoveEffect(battlerAtk, EFFECT_BATON_PASS) || IsBattlerTrapped(battlerAtk, TRUE)) return score; diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 68450e4e59..382b8ebfea 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -1040,90 +1040,6 @@ u32 GetNoOfHitsToKOBattler(u32 battlerAtk, u32 battlerDef, u32 moveIndex) return GetNoOfHitsToKOBattlerDmg(AI_DATA->simulatedDmg[battlerAtk][battlerDef][moveIndex], battlerDef); } -void SetMovesDamageResults(u32 battlerAtk, u16 *moves) -{ - s32 i, j, battlerDef, bestId, currId, hp, result; - s32 moveDmgs[MAX_MON_MOVES]; - bool32 isNotConsidered[MAX_MON_MOVES]; - - for (i = 0; i < MAX_MON_MOVES; i++) - { - u32 move = moves[i]; - if (move == MOVE_NONE || move == MOVE_UNAVAILABLE || gBattleMoves[move].power == 0) - isNotConsidered[i] = TRUE; - else - isNotConsidered[i] = FALSE; - } - - for (i = 0; i < MAX_MON_MOVES; i++) - { - for (battlerDef = 0; battlerDef < MAX_BATTLERS_COUNT; battlerDef++) - { - if (battlerDef == battlerAtk) - continue; - - if (isNotConsidered[i]) - { - result = MOVE_POWER_OTHER; // Move has a power of 0/1 - } - else - { - // Check all other moves and calculate their power - for (j = 0; j < MAX_MON_MOVES; j++) - { - if (!isNotConsidered[j]) - moveDmgs[j] = AI_DATA->simulatedDmg[battlerAtk][battlerDef][j]; - else - moveDmgs[j] = 0; - } - - hp = gBattleMons[battlerDef].hp + (20 * gBattleMons[battlerDef].hp / 100); // 20 % add to make sure the battler is always fainted - // If a move can faint battler, it doesn't matter how much damage it does - for (j = 0; j < MAX_MON_MOVES; j++) - { - if (moveDmgs[j] > hp) - moveDmgs[j] = hp; - } - - // Find move which deals most damage, in case of a tie prioritize one with better effect. - for (bestId = 0, j = 1; j < MAX_MON_MOVES; j++) - { - if (moveDmgs[j] > moveDmgs[bestId]) - bestId = j; - if (moveDmgs[j] == moveDmgs[bestId]) - { - switch (AI_WhichMoveBetter(gBattleMons[battlerAtk].moves[bestId], gBattleMons[battlerAtk].moves[j], battlerAtk, battlerDef, GetNoOfHitsToKO(moveDmgs[j], hp))) - { - case 2: - if (Random() & 1) - break; - case 1: - bestId = j; - break; - } - } - } - - currId = i; - if (currId == bestId) - result = MOVE_POWER_BEST; - else if ((moveDmgs[currId] >= hp || moveDmgs[bestId] < hp) // If current move can faint as well, or if neither can - && GetNoOfHitsToKO(moveDmgs[currId], hp) - GetNoOfHitsToKO(moveDmgs[bestId], hp) <= 2 // Consider a move weak if it needs to be used at least 2 times more to faint the target, compared to the best move. - && AI_WhichMoveBetter(gBattleMons[battlerAtk].moves[bestId], gBattleMons[battlerAtk].moves[currId], battlerAtk, battlerDef, GetNoOfHitsToKO(moveDmgs[currId], hp)) != 0) - result = MOVE_POWER_GOOD; - else - result = MOVE_POWER_WEAK; - } - AI_DATA->moveDmgResult[battlerAtk][battlerDef][i] = result; - } - } -} - -u32 GetMoveDamageResult(u32 battlerAtk, u32 battlerDef, u32 moveIndex) -{ - return AI_DATA->moveDmgResult[battlerAtk][battlerDef][moveIndex]; -} - u32 GetCurrDamageHpPercent(u32 battlerAtk, u32 battlerDef) { int bestDmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]; diff --git a/test/battle/ai.c b/test/battle/ai.c index 91a9bc663a..0fa2664358 100644 --- a/test/battle/ai.c +++ b/test/battle/ai.c @@ -223,8 +223,7 @@ AI_SINGLE_BATTLE_TEST("AI prefers a weaker move over a one with a downside effec TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); } break; } - } - SCENE { + } SCENE { MESSAGE("Wobbuffet fainted!"); } } @@ -248,7 +247,7 @@ 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) { HP(278); }; PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_KANGASKHAN) { Moves(MOVE_STRENGTH, MOVE_HORN_ATTACK, MOVE_SWORDS_DANCE); } } WHEN { diff --git a/test/battle/ai_check_viability.c b/test/battle/ai_check_viability.c index 11052fb163..fa46e2a9f0 100644 --- a/test/battle/ai_check_viability.c +++ b/test/battle/ai_check_viability.c @@ -140,6 +140,28 @@ AI_SINGLE_BATTLE_TEST("AI sees increased base power of Spit Up") TURN { EXPECT_MOVE(opponent, MOVE_STOCKPILE); } TURN { EXPECT_MOVE(opponent, MOVE_SPIT_UP); } } SCENE { - // MESSAGE("Wobbuffet fainted!"); + MESSAGE("Wobbuffet fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI can choose Counter or Mirror Coat if the predicted move split is correct and user doesn't faint") +{ + u16 playerMove = MOVE_NONE, opponentMove = MOVE_NONE; + + PARAMETRIZE { playerMove = MOVE_STRENGTH; opponentMove = MOVE_COUNTER; } + PARAMETRIZE { playerMove = MOVE_POWER_GEM; opponentMove = MOVE_MIRROR_COAT; } + + GIVEN { + ASSUME(gBattleMoves[MOVE_COUNTER].effect == EFFECT_COUNTER); + ASSUME(gBattleMoves[MOVE_MIRROR_COAT].effect == EFFECT_MIRROR_COAT); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { HP(102); Speed(100); Moves(opponentMove, MOVE_STRENGTH); } + } WHEN { + TURN { MOVE(player, playerMove); EXPECT_MOVE(opponent, MOVE_STRENGTH); } + TURN { MOVE(player, playerMove); EXPECT_MOVE(opponent, opponentMove); } + TURN { MOVE(player, playerMove); EXPECT_MOVE(opponent, MOVE_STRENGTH); } + } SCENE { + MESSAGE("Foe Wobbuffet fainted!"); } } diff --git a/test/battle/ai_trytofaint.c b/test/battle/ai_trytofaint.c new file mode 100644 index 0000000000..bbfbee19d1 --- /dev/null +++ b/test/battle/ai_trytofaint.c @@ -0,0 +1,48 @@ +#include "global.h" +#include "test/battle.h" +#include "battle_ai_util.h" + +AI_SINGLE_BATTLE_TEST("AI prefers priority moves if it's slower and can kill target") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_QUICK_ATTACK].priority == 1); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(100); } + PLAYER(SPECIES_WOBBUFFET) { Speed(100); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); Moves(MOVE_QUICK_ATTACK, MOVE_STRENGTH); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_QUICK_ATTACK); SEND_OUT(player, 1); } + } SCENE { + MESSAGE("Wobbuffet fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI will choose a random move if it's faster and can kill target") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_QUICK_ATTACK].priority == 1); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); Moves(MOVE_QUICK_ATTACK, MOVE_STRENGTH); } + } WHEN { + TURN { EXPECT_MOVES(opponent, MOVE_QUICK_ATTACK, MOVE_STRENGTH); SEND_OUT(player, 1); } + } SCENE { + MESSAGE("Wobbuffet fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI will choose a priority move if it is slower then the target and will be killed") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_QUICK_ATTACK].priority == 1); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { Speed(100); } + OPPONENT(SPECIES_WOBBUFFET) { HP(60); Speed(1); Moves(MOVE_QUICK_ATTACK, MOVE_STRENGTH); } + } WHEN { + TURN { MOVE(player, MOVE_STRENGTH); EXPECT_MOVE(opponent, MOVE_STRENGTH); } + TURN { MOVE(player, MOVE_STRENGTH); EXPECT_MOVE(opponent, MOVE_QUICK_ATTACK); } + } SCENE { + MESSAGE("Foe Wobbuffet fainted!"); + } +}