From 91b364e8d6d97bfab8f2a7fd1935ba6ca6544a60 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Tue, 27 Aug 2019 18:29:34 +0200 Subject: [PATCH] Ai use most powerful move smarrter --- data/battle_ai_scripts.s | 50 ++++--- include/battle.h | 2 +- include/battle_util.h | 1 + include/constants/battle_ai.h | 5 +- src/battle_ai_script_commands.c | 229 +++++++++++++------------------- 5 files changed, 129 insertions(+), 158 deletions(-) diff --git a/data/battle_ai_scripts.s b/data/battle_ai_scripts.s index 0617e3b367..31e91f1356 100644 --- a/data/battle_ai_scripts.s +++ b/data/battle_ai_scripts.s @@ -50,7 +50,7 @@ AI_CheckBadMove: if_move MOVE_FISSURE, AI_CBM_CheckIfNegatesType if_move MOVE_HORN_DRILL, AI_CBM_CheckIfNegatesType get_how_powerful_move_is - if_equal 0, AI_CheckBadMove_CheckEffect + if_equal MOVE_POWER_DISCOURAGED, AI_CheckBadMove_CheckEffect AI_CBM_CheckIfNegatesType: @ 82DBF92 if_type_effectiveness AI_EFFECTIVENESS_x0, Score_Minus10 @@ -934,12 +934,21 @@ AI_CheckIfAlreadyDeadPriorities: if_random_less_than 126, AI_Ret score +1 end + +@ The purpose is to use a move effect that hits the hardest or similar +AI_CV_DmgMove: + get_considered_move_power + if_equal 0, AI_Ret + get_how_powerful_move_is + if_equal MOVE_POWER_WEAK, Score_Minus1 + end AI_CheckViability: if_target_is_ally AI_Ret call_if_always_hit AI_CV_AlwaysHit call_if_move_flag FLAG_HIGH_CRIT, AI_CV_HighCrit call AI_CheckIfAlreadyDead + call AI_CV_DmgMove if_effect EFFECT_HIT, AI_CV_Hit if_effect EFFECT_SLEEP, AI_CV_Sleep if_effect EFFECT_ABSORB, AI_CV_Absorb @@ -3218,31 +3227,32 @@ AI_CV_DragonDance_End: AI_TryToFaint: if_target_is_ally AI_Ret - if_can_faint AI_TryToFaint_TryToEncourageQuickAttack + if_can_faint AI_TryToFaint_Can get_how_powerful_move_is - if_equal MOVE_NOT_MOST_POWERFUL, Score_Minus1 + if_equal MOVE_POWER_DISCOURAGED, Score_Minus1 +AI_TryToFaint2: if_type_effectiveness AI_EFFECTIVENESS_x4, AI_TryToFaint_DoubleSuperEffective - goto AI_TryToFaint_InDanger + goto AI_TryToFaint_CheckIfDanger AI_TryToFaint_DoubleSuperEffective: - if_random_less_than 80, AI_TryToFaint_InDanger + if_random_less_than 80, AI_TryToFaint_CheckIfDanger score +2 - goto AI_TryToFaint_InDanger -AI_TryToFaint_TryToEncourageQuickAttack: - if_effect EFFECT_EXPLOSION, AI_TryToFaint_InDanger + goto AI_TryToFaint_CheckIfDanger +AI_TryToFaint_Can: + if_effect EFFECT_EXPLOSION, AI_TryToFaint_CheckIfDanger if_user_faster AI_TryToFaint_ScoreUp4 if_move_flag FLAG_HIGH_CRIT AI_TryToFaint_ScoreUp4 score +2 - goto AI_TryToFaint_InDanger + goto AI_TryToFaint_CheckIfDanger AI_TryToFaint_ScoreUp4: score +4 -AI_TryToFaint_InDanger: - if_target_faster AI_TryToFaint_End - if_ai_can_go_down AI_TryToFaint_IsInDanger +AI_TryToFaint_CheckIfDanger: + if_user_faster AI_TryToFaint_End + if_ai_can_go_down AI_TryToFaint_Danger AI_TryToFaint_End: end -AI_TryToFaint_IsInDanger: +AI_TryToFaint_Danger: get_how_powerful_move_is - if_not_equal MOVE_MOST_POWERFUL, Score_Minus1 + if_not_equal MOVE_POWER_BEST, Score_Minus1 score +1 goto AI_TryToFaint_End @@ -3339,10 +3349,9 @@ AI_SetupFirstTurn_SetupEffectsToEncourage: AI_PreferStrongestMove: if_target_is_ally AI_Ret get_how_powerful_move_is - if_not_equal 0, AI_PreferStrongestMove_End + if_not_equal MOVE_POWER_BEST, AI_PreferStrongestMove_End if_random_less_than 100, AI_PreferStrongestMove_End score +2 - AI_PreferStrongestMove_End: end @@ -3354,7 +3363,6 @@ AI_Risky: AI_Risky_RandChance: if_random_less_than 128, AI_Risky_End score +2 - AI_Risky_End: end @@ -3384,7 +3392,7 @@ AI_PreferBatonPass: count_usable_party_mons AI_USER if_equal 0, AI_PreferBatonPassEnd get_how_powerful_move_is - if_not_equal 0, AI_PreferBatonPassEnd + if_not_equal MOVE_POWER_DISCOURAGED, AI_PreferBatonPassEnd if_has_move_with_effect AI_USER, EFFECT_BATON_PASS, AI_PreferBatonPass_GoForBatonPass if_random_less_than 80, AI_Risky_End @@ -3488,7 +3496,7 @@ AI_DoubleBattle: AI_DoubleBattlePartnerHasHelpingHand: get_how_powerful_move_is - if_not_equal 0, Score_Plus1 + if_not_equal MOVE_POWER_DISCOURAGED, Score_Plus1 end AI_DoubleBattleCheckUserStatus: @@ -3499,7 +3507,7 @@ AI_DoubleBattleCheckUserStatus2: get_how_powerful_move_is if_equal MOVE_POWER_DISCOURAGED, Score_Minus5 score +1 - if_equal MOVE_MOST_POWERFUL, Score_Plus2 + if_equal MOVE_POWER_BEST, Score_Plus2 end AI_DoubleBattleAllHittingGroundMove: @@ -3537,7 +3545,7 @@ AI_DoubleBattleFireMove2: AI_TryOnAlly: get_how_powerful_move_is - if_equal 0, AI_TryStatusMoveOnAlly + if_equal MOVE_POWER_DISCOURAGED, AI_TryStatusMoveOnAlly get_curr_move_type if_equal TYPE_FIRE, AI_TryFireMoveOnAlly diff --git a/include/battle.h b/include/battle.h index f5cb1317ab..2e6d7586bc 100644 --- a/include/battle.h +++ b/include/battle.h @@ -251,7 +251,7 @@ struct AI_ThinkingStruct u32 aiFlags; u8 aiAction; u8 aiLogicId; - u8 simulatedRNG[4]; + s32 simulatedDmg[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, move struct AI_SavedBattleMon saved[4]; bool8 switchMon; // Because all available moves have no/little effect. }; diff --git a/include/battle_util.h b/include/battle_util.h index 8b3fa9de33..58967f5e24 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -84,6 +84,7 @@ void BattleScriptPushCursorAndCallback(const u8* BS_ptr); u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn); void ClearFuryCutterDestinyBondGrudge(u8 battlerId); void HandleAction_RunBattleScript(void); +u32 SetRandomTarget(u32 battlerId); u8 GetMoveTarget(u16 move, u8 setTarget); u8 IsMonDisobedient(void); u32 GetBattlerHoldEffect(u8 battlerId, bool32 checkNegating); diff --git a/include/constants/battle_ai.h b/include/constants/battle_ai.h index 238f5fc4cc..974ef2b770 100644 --- a/include/constants/battle_ai.h +++ b/include/constants/battle_ai.h @@ -31,8 +31,9 @@ // get_how_powerful_move_is #define MOVE_POWER_DISCOURAGED 0 -#define MOVE_NOT_MOST_POWERFUL 1 -#define MOVE_MOST_POWERFUL 2 +#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. // script's table id to bit #define AI_SCRIPT_CHECK_BAD_MOVE (1 << 0) diff --git a/src/battle_ai_script_commands.c b/src/battle_ai_script_commands.c index c33cfaaf25..0aa2f3aa91 100644 --- a/src/battle_ai_script_commands.c +++ b/src/battle_ai_script_commands.c @@ -14,6 +14,7 @@ #include "constants/abilities.h" #include "constants/battle_ai.h" #include "constants/battle_move_effects.h" +#include "constants/hold_effects.h" #include "constants/moves.h" #include "constants/species.h" @@ -55,6 +56,7 @@ static void BattleAI_DoAIProcessing(void); static void AIStackPushVar(const u8 *); static bool8 AIStackPop(void); static s32 CountUsablePartyMons(u8 battlerId); +static s32 AI_GetAbility(u32 battlerId, bool32 guess); static void BattleAICmd_if_random_less_than(void); static void BattleAICmd_if_random_greater_than(void); @@ -360,7 +362,7 @@ void BattleAI_SetupFlags(void) void BattleAI_SetupAIData(u8 defaultScoreMoves) { - s32 i; + s32 i, move, dmg; u8 moveLimitations; // Clear AI data but preserve the flags. @@ -386,25 +388,32 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves) { if (gBitTable[i] & moveLimitations) AI_THINKING_STRUCT->score[i] = 0; - - AI_THINKING_STRUCT->simulatedRNG[i] = 100 - (Random() % 16); } gBattleResources->AI_ScriptsStack->size = 0; sBattler_AI = gActiveBattler; - // Decide a random target battlerId in doubles. - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + // Simulate dmg for all AI moves against all opposing targets + for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++) { - gBattlerTarget = (Random() & BIT_FLANK) + (GetBattlerSide(gActiveBattler) ^ BIT_SIDE); - if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) - gBattlerTarget ^= BIT_FLANK; - } - // There's only one choice in single battles. - else - { - gBattlerTarget = sBattler_AI ^ BIT_SIDE; + if (GET_BATTLER_SIDE2(sBattler_AI) == GET_BATTLER_SIDE2(gBattlerTarget)) + continue; + for (i = 0; i < MAX_MON_MOVES; i++) + { + dmg = 0; + move = gBattleMons[sBattler_AI].moves[i]; + if (gBattleMoves[move].power != 0 && !(moveLimitations & gBitTable[i])) + { + dmg = AI_CalcDamage(move, sBattler_AI, gBattlerTarget) * (100 - (Random() % 16)) / 100; + if (dmg == 0) + dmg = 1; + } + + AI_THINKING_STRUCT->simulatedDmg[sBattler_AI][gBattlerTarget][i] = dmg; + } } + + gBattlerTarget = SetRandomTarget(sBattler_AI); } u8 BattleAI_ChooseMoveOrAction(void) @@ -1322,9 +1331,38 @@ static void BattleAICmd_get_considered_move_power(void) gAIScriptPtr += 1; } +// Checks if the move dealing less damage does not have worse effects. +static bool32 CompareTwoMoves(u32 bestMove, u32 goodMove) +{ + s32 defAbility = AI_GetAbility(gBattlerTarget, FALSE); + + // Check if physical moves hurt. + if (GetBattlerHoldEffect(sBattler_AI, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS + && (BATTLE_HISTORY->itemEffects[gBattlerTarget] == HOLD_EFFECT_ROCKY_HELMET + || defAbility == ABILITY_IRON_BARBS || defAbility == ABILITY_ROUGH_SKIN)) + { + if (IS_MOVE_PHYSICAL(goodMove) && !IS_MOVE_PHYSICAL(bestMove)) + return FALSE; + } + // Check recoil + if (GetBattlerAbility(sBattler_AI) != ABILITY_ROCK_HEAD) + { + if (gBattleMoves[goodMove].effect == EFFECT_RECOIL && gBattleMoves[bestMove].effect != EFFECT_RECOIL) + return FALSE; + } + // Check recharge + if (gBattleMoves[goodMove].effect == EFFECT_RECHARGE && gBattleMoves[bestMove].effect != EFFECT_RECHARGE) + return FALSE; + // Check additional effect. + if (gBattleMoves[bestMove].effect != 0 && gBattleMoves[goodMove].effect == 0) + return FALSE; + + return TRUE; +} + static void BattleAICmd_get_how_powerful_move_is(void) { - s32 i, checkedMove; + s32 i, checkedMove, bestId, currId, hp; s32 moveDmgs[4]; for (i = 0; sDiscouragedPowerfulMoveEffects[i] != 0xFFFF; i++) @@ -1333,12 +1371,9 @@ static void BattleAICmd_get_how_powerful_move_is(void) break; } - if (gBattleMoves[AI_THINKING_STRUCT->moveConsidered].power > 1 + if (gBattleMoves[AI_THINKING_STRUCT->moveConsidered].power != 0 && sDiscouragedPowerfulMoveEffects[i] == 0xFFFF) { - *(&gBattleStruct->dynamicMoveType) = 0; - gMoveResultFlags = 0; - for (checkedMove = 0; checkedMove < MAX_MON_MOVES; checkedMove++) { for (i = 0; sDiscouragedPowerfulMoveEffects[i] != 0xFFFF; i++) @@ -1349,13 +1384,9 @@ static void BattleAICmd_get_how_powerful_move_is(void) if (gBattleMons[sBattler_AI].moves[checkedMove] != MOVE_NONE && sDiscouragedPowerfulMoveEffects[i] == 0xFFFF - && gBattleMoves[gBattleMons[sBattler_AI].moves[checkedMove]].power > 1) + && gBattleMoves[gBattleMons[sBattler_AI].moves[checkedMove]].power != 0) { - gCurrentMove = gBattleMons[sBattler_AI].moves[checkedMove]; - moveDmgs[checkedMove] = AI_CalcDamage(gCurrentMove, sBattler_AI, gBattlerTarget); - moveDmgs[checkedMove] = moveDmgs[checkedMove] * AI_THINKING_STRUCT->simulatedRNG[checkedMove] / 100; - if (moveDmgs[checkedMove] == 0) - moveDmgs[checkedMove] = 1; + moveDmgs[checkedMove] = AI_THINKING_STRUCT->simulatedDmg[sBattler_AI][gBattlerTarget][checkedMove]; } else { @@ -1363,16 +1394,22 @@ static void BattleAICmd_get_how_powerful_move_is(void) } } - for (checkedMove = 0; checkedMove < MAX_MON_MOVES; checkedMove++) + for (bestId = 0, i = 0; i < MAX_MON_MOVES; i++) { - if (moveDmgs[checkedMove] > moveDmgs[AI_THINKING_STRUCT->movesetIndex]) - break; + if (moveDmgs[i] > moveDmgs[bestId]) + bestId = i; } - if (checkedMove == MAX_MON_MOVES) - AI_THINKING_STRUCT->funcResult = MOVE_MOST_POWERFUL; // Is the most powerful. + currId = AI_THINKING_STRUCT->movesetIndex; + hp = gBattleMons[gBattlerTarget].hp; + if (currId == bestId) + AI_THINKING_STRUCT->funcResult = MOVE_POWER_BEST; + // Compare percentage difference. + else if ((moveDmgs[bestId] * 100 / hp) - (moveDmgs[currId] * 100 / hp) <= 10 + && CompareTwoMoves(gBattleMons[sBattler_AI].moves[bestId], gBattleMons[sBattler_AI].moves[currId])) + AI_THINKING_STRUCT->funcResult = MOVE_POWER_GOOD; else - AI_THINKING_STRUCT->funcResult = MOVE_NOT_MOST_POWERFUL; // Not the most powerful. + AI_THINKING_STRUCT->funcResult = MOVE_POWER_WEAK; } else { @@ -1516,109 +1553,49 @@ static void BattleAICmd_get_considered_move_effect(void) gAIScriptPtr += 1; } -static void BattleAICmd_get_ability(void) +static s32 AI_GetAbility(u32 battlerId, bool32 guess) { - u8 battlerId = BattleAI_GetWantedBattler(gAIScriptPtr[1]); + // The AI knows its own ability. + if (IsBattlerAIControlled) + return gBattleMons[battlerId].ability; - if (!IsBattlerAIControlled(battlerId)) + if (BATTLE_HISTORY->abilities[battlerId] != 0) + return BATTLE_HISTORY->abilities[battlerId]; + + // Abilities that prevent fleeing. + if (gBattleMons[battlerId].ability == ABILITY_SHADOW_TAG + || gBattleMons[battlerId].ability == ABILITY_MAGNET_PULL + || gBattleMons[battlerId].ability == ABILITY_ARENA_TRAP) + return gBattleMons[battlerId].ability; + + if (gBaseStats[gBattleMons[battlerId].species].abilities[0] != ABILITY_NONE) { - if (BATTLE_HISTORY->abilities[battlerId] != 0) + if (gBaseStats[gBattleMons[battlerId].species].abilities[1] != ABILITY_NONE) { - AI_THINKING_STRUCT->funcResult = BATTLE_HISTORY->abilities[battlerId]; - gAIScriptPtr += 2; - return; - } - - // abilities that prevent fleeing. - if (gBattleMons[battlerId].ability == ABILITY_SHADOW_TAG - || gBattleMons[battlerId].ability == ABILITY_MAGNET_PULL - || gBattleMons[battlerId].ability == ABILITY_ARENA_TRAP) - { - AI_THINKING_STRUCT->funcResult = gBattleMons[battlerId].ability; - gAIScriptPtr += 2; - return; - } - - if (gBaseStats[gBattleMons[battlerId].species].abilities[0] != ABILITY_NONE) - { - if (gBaseStats[gBattleMons[battlerId].species].abilities[1] != ABILITY_NONE) - { - // AI has no knowledge of opponent, so it guesses which ability. - if (Random() & 1) - AI_THINKING_STRUCT->funcResult = gBaseStats[gBattleMons[battlerId].species].abilities[0]; - else - AI_THINKING_STRUCT->funcResult = gBaseStats[gBattleMons[battlerId].species].abilities[1]; - } - else - { - AI_THINKING_STRUCT->funcResult = gBaseStats[gBattleMons[battlerId].species].abilities[0]; // It's definitely ability 1. - } + // AI has no knowledge of opponent, so it guesses which ability. + if (guess) + return gBaseStats[gBattleMons[battlerId].species].abilities[Random() & 1]; } else { - AI_THINKING_STRUCT->funcResult = gBaseStats[gBattleMons[battlerId].species].abilities[1]; // AI can't actually reach this part since no pokemon has ability 2 and no ability 1. + return gBaseStats[gBattleMons[battlerId].species].abilities[0]; // It's definitely ability 1. } } - else - { - // The AI knows its own ability. - AI_THINKING_STRUCT->funcResult = gBattleMons[battlerId].ability; - } + return -1; // Unknown. +} +static void BattleAICmd_get_ability(void) +{ + AI_THINKING_STRUCT->funcResult = AI_GetAbility(BattleAI_GetWantedBattler(gAIScriptPtr[1]), TRUE); gAIScriptPtr += 2; } static void BattleAICmd_check_ability(void) { u32 battlerId = BattleAI_GetWantedBattler(gAIScriptPtr[1]); - u32 ability = gAIScriptPtr[2]; + u32 ability = AI_GetAbility(battlerId, FALSE); - if (!IsBattlerAIControlled(battlerId)) - { - if (BATTLE_HISTORY->abilities[battlerId] != ABILITY_NONE) - { - ability = BATTLE_HISTORY->abilities[battlerId]; - AI_THINKING_STRUCT->funcResult = ability; - } - // Abilities that prevent fleeing. - else if (gBattleMons[battlerId].ability == ABILITY_SHADOW_TAG - || gBattleMons[battlerId].ability == ABILITY_MAGNET_PULL - || gBattleMons[battlerId].ability == ABILITY_ARENA_TRAP) - { - ability = gBattleMons[battlerId].ability; - } - else if (gBaseStats[gBattleMons[battlerId].species].abilities[0] != ABILITY_NONE) - { - if (gBaseStats[gBattleMons[battlerId].species].abilities[1] != ABILITY_NONE) - { - u8 abilityDummyVariable = ability; // Needed to match. - if (gBaseStats[gBattleMons[battlerId].species].abilities[0] != abilityDummyVariable - && gBaseStats[gBattleMons[battlerId].species].abilities[1] != abilityDummyVariable) - { - ability = gBaseStats[gBattleMons[battlerId].species].abilities[0]; - } - else - { - ability = ABILITY_NONE; - } - } - else - { - ability = gBaseStats[gBattleMons[battlerId].species].abilities[0]; - } - } - else - { - ability = gBaseStats[gBattleMons[battlerId].species].abilities[1]; // AI can't actually reach this part since no pokemon has ability 2 and no ability 1. - } - } - else - { - // The AI knows its own or partner's ability. - ability = gBattleMons[battlerId].ability; - } - - if (ability == 0) + if (ability == -1) AI_THINKING_STRUCT->funcResult = 2; // Unable to answer. else if (ability == gAIScriptPtr[2]) AI_THINKING_STRUCT->funcResult = 1; // Pokemon has the ability we wanted to check. @@ -1878,15 +1855,7 @@ static void BattleAICmd_if_can_faint(void) return; } - gBattleStruct->dynamicMoveType = 0; - gMoveResultFlags = 0; - dmg = AI_CalcDamage(AI_THINKING_STRUCT->moveConsidered, sBattler_AI, gBattlerTarget); - dmg = dmg * AI_THINKING_STRUCT->simulatedRNG[AI_THINKING_STRUCT->movesetIndex] / 100; - - // Moves always do at least 1 damage. - if (dmg == 0) - dmg = 1; - + dmg = AI_THINKING_STRUCT->simulatedDmg[sBattler_AI][gBattlerTarget][AI_THINKING_STRUCT->movesetIndex]; if (gBattleMons[gBattlerTarget].hp <= dmg) gAIScriptPtr = T1_READ_PTR(gAIScriptPtr + 1); else @@ -1903,15 +1872,7 @@ static void BattleAICmd_if_cant_faint(void) return; } - gBattleStruct->dynamicMoveType = 0; - gMoveResultFlags = 0; - dmg = AI_CalcDamage(AI_THINKING_STRUCT->moveConsidered, sBattler_AI, gBattlerTarget); - dmg = dmg * AI_THINKING_STRUCT->simulatedRNG[AI_THINKING_STRUCT->movesetIndex] / 100; - - // Moves always do at least 1 damage. - if (dmg == 0) - dmg = 1; - + dmg = AI_THINKING_STRUCT->simulatedDmg[sBattler_AI][gBattlerTarget][AI_THINKING_STRUCT->movesetIndex]; if (gBattleMons[gBattlerTarget].hp > dmg) gAIScriptPtr = T1_READ_PTR(gAIScriptPtr + 1); else