diff --git a/include/battle.h b/include/battle.h index a95dfc9066..49f763f30b 100644 --- a/include/battle.h +++ b/include/battle.h @@ -352,6 +352,7 @@ struct AiLogicData u8 shouldSwitch; // Stores result of ShouldSwitch, which decides whether a mon should be switched out u8 aiCalcInProgress:1; u8 battlerDoingPrediction; // Stores which battler is currently running its prediction calcs + u16 predictedMove[MAX_BATTLERS_COUNT]; }; struct AI_ThinkingStruct diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index 35edd0c10e..13d13c8298 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -122,5 +122,6 @@ void Ai_UpdateSwitchInData(u32 battler); void Ai_UpdateFaintData(u32 battler); void SetAiLogicDataForTurn(struct AiLogicData *aiData); void ResetDynamicAiFunc(void); +u32 BattleAI_PredictMove(u32 battler, u32 opposingBattler); #endif // GUARD_BATTLE_AI_MAIN_H diff --git a/include/config/general.h b/include/config/general.h index cff1432bb7..bd34106832 100644 --- a/include/config/general.h +++ b/include/config/general.h @@ -6,7 +6,7 @@ // still has them in the ROM. This is because the developers forgot // to define NDEBUG before release, however this has been changed as // Ruby's actual debug build does not use the AGBPrint features. -#define NDEBUG +// #define NDEBUG // To enable printf debugging, comment out "#define NDEBUG". This allows // the various AGBPrint functions to be used. (See include/gba/isagbprint.h). diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index ac474b1066..22ae7a1fbb 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -35,6 +35,7 @@ static u32 ChooseMoveOrAction_Singles(u32 battlerAi); static u32 ChooseMoveOrAction_Doubles(u32 battlerAi); static inline void BattleAI_DoAIProcessing(struct AI_ThinkingStruct *aiThink, u32 battlerAi, u32 battlerDef); +static inline void BattleAI_DoAIProcessing_PredictMove(struct AI_ThinkingStruct *aiThink, u32 battler, u32 opposingBattler); static inline void BattleAI_DoAIProcessing_PredictedSwitchin(struct AI_ThinkingStruct *aiThink, struct AiLogicData *aiData, u32 battlerAi, u32 battlerDef); static bool32 IsPinchBerryItemEffect(u32 holdEffect); @@ -239,6 +240,7 @@ void BattleAI_SetupFlags(void) void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) { u32 moveLimitations, moveLimitationsTarget; + u8 defaultScoreMovesTarget = defaultScoreMoves; u32 flags[MAX_BATTLERS_COUNT]; u32 moveIndex; @@ -268,16 +270,19 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) // Initialize move prediction scores if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES) { - moveLimitationsTarget = AI_DATA->moveLimitations[gBattlerTarget]; + u32 opposingBattler = GetOppositeBattler(battler); + moveLimitationsTarget = AI_DATA->moveLimitations[opposingBattler]; for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) { - if (moveLimitations & (1u << moveIndex)) - SET_PREDICTED_SCORE(gBattlerTarget, moveIndex, 0); - if (defaultScoreMoves & 1) - SET_PREDICTED_SCORE(gBattlerTarget, moveIndex, AI_SCORE_DEFAULT); + if (moveLimitationsTarget & (1u << moveIndex)) + SET_PREDICTED_SCORE(opposingBattler, moveIndex, 0); + if (defaultScoreMovesTarget & 1) + SET_PREDICTED_SCORE(opposingBattler, moveIndex, AI_SCORE_DEFAULT); else - SET_PREDICTED_SCORE(gBattlerTarget, moveIndex, 0); + SET_PREDICTED_SCORE(opposingBattler, moveIndex, 0); + + defaultScoreMovesTarget >>= 1; } } } @@ -501,6 +506,56 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData) AI_DATA->aiCalcInProgress = FALSE; } +u32 BattleAI_PredictMove(u32 battler, u32 opposingBattler) +{ + u8 currentMoveArray[MAX_MON_MOVES]; + u8 consideredMoveArray[MAX_MON_MOVES]; + u32 numOfBestMoves; + s32 i; + u32 flags = AI_THINKING_STRUCT->aiFlags[battler]; + + AI_DATA->partnerMove = 0; // no ally + while (flags != 0) + { + if (flags & 1) + { + BattleAI_DoAIProcessing_PredictMove(AI_THINKING_STRUCT, battler, opposingBattler); + } + flags >>= 1; + AI_THINKING_STRUCT->aiLogicId++; + } + + for (i = 0; i < MAX_MON_MOVES; i++) + { + gAiBattleData->finalScore[opposingBattler][battler][i] = AI_THINKING_STRUCT->predictedScore[i][opposingBattler]; + DebugPrintf("Final score: %d", gAiBattleData->finalScore[opposingBattler][battler][i]); + } + + numOfBestMoves = 1; + currentMoveArray[0] = AI_THINKING_STRUCT->predictedScore[0][opposingBattler]; + consideredMoveArray[0] = 0; + + for (i = 1; i < MAX_MON_MOVES; i++) + { + if (gBattleMons[opposingBattler].moves[i] != MOVE_NONE) + { + // In ruby, the order of these if statements is reversed. + if (currentMoveArray[0] == AI_THINKING_STRUCT->predictedScore[i][opposingBattler]) + { + currentMoveArray[numOfBestMoves] = AI_THINKING_STRUCT->predictedScore[i][opposingBattler]; + consideredMoveArray[numOfBestMoves++] = i; + } + if (currentMoveArray[0] < AI_THINKING_STRUCT->predictedScore[i][opposingBattler]) + { + numOfBestMoves = 1; + currentMoveArray[0] = AI_THINKING_STRUCT->predictedScore[i][opposingBattler]; + consideredMoveArray[0] = i; + } + } + } + return consideredMoveArray[Random() % numOfBestMoves]; +} + static u32 ChooseMoveOrAction_Singles(u32 battlerAi) { u8 currentMoveArray[MAX_MON_MOVES]; @@ -692,6 +747,44 @@ static inline bool32 ShouldConsiderMoveForBattler(u32 battlerAi, u32 battlerDef, return TRUE; } +static inline void BattleAI_DoAIProcessing_PredictMove(struct AI_ThinkingStruct *aiThink, u32 battler, u32 opposingBattler) +{ + do + { + if (gBattleMons[opposingBattler].pp[aiThink->movesetIndex] == 0) + aiThink->moveConsidered = MOVE_NONE; + else + aiThink->moveConsidered = gBattleMons[opposingBattler].moves[aiThink->movesetIndex]; + + DebugPrintf("Move considered: %d", aiThink->moveConsidered); + + // There is no point in calculating scores for all 3 battlers(2 opponents + 1 ally) with certain moves. + if (aiThink->moveConsidered != MOVE_NONE + && aiThink->score[aiThink->movesetIndex] > 0 + && ShouldConsiderMoveForBattler(opposingBattler, battler, aiThink->moveConsidered)) + { + if (aiThink->aiLogicId < ARRAY_COUNT(sBattleAiFuncTable) + && sBattleAiFuncTable[aiThink->aiLogicId] != NULL) + { + // Call AI function + aiThink->predictedScore[aiThink->movesetIndex][opposingBattler] = + sBattleAiFuncTable[aiThink->aiLogicId](opposingBattler, + battler, + aiThink->moveConsidered, + aiThink->predictedScore[aiThink->movesetIndex][opposingBattler]); + DebugPrintf("Current score: %d", aiThink->predictedScore[aiThink->movesetIndex][opposingBattler]); + } + } + else + { + aiThink->predictedScore[aiThink->movesetIndex][opposingBattler] = 0; + } + aiThink->movesetIndex++; + } while (aiThink->movesetIndex < MAX_MON_MOVES); + + aiThink->movesetIndex = 0; +} + static inline void BattleAI_DoAIProcessing(struct AI_ThinkingStruct *aiThink, u32 battlerAi, u32 battlerDef) { do diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index da91175f55..f05d9078de 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -448,7 +448,7 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) struct Pokemon *party; u16 monAbility, aiMove; u32 opposingBattler = GetOppositeBattler(battler); - u32 incomingMove = AI_DATA->lastUsedMove[opposingBattler]; + u32 incomingMove = (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES) ? AI_DATA->predictedMove[opposingBattler] : AI_DATA->lastUsedMove[opposingBattler]; u32 incomingType = GetMoveType(incomingMove); u32 predictedMove = incomingMove; // Update for move prediction u32 predictedType = GetMoveType(predictedMove); diff --git a/src/battle_main.c b/src/battle_main.c index 6845190d10..e2a19ae40f 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4142,10 +4142,8 @@ enum STATE_SELECTION_SCRIPT_MAY_RUN }; -void SetupAISwitchingData(u32 battler, enum SwitchType switchType) +void SetupAISwitchingData(u32 battler, u32 opposingBattler, enum SwitchType switchType) { - s32 opposingBattler = GetOppositeBattler(battler); - // AI's predicting data if ((AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_SWITCH)) { @@ -4188,10 +4186,17 @@ static void HandleTurnActionSelectionState(void) && (BattlerHasAi(battler) && !(gBattleTypeFlags & BATTLE_TYPE_PALACE))) { AI_DATA->aiCalcInProgress = TRUE; + u32 opposingBattler = GetOppositeBattler(battler); // Setup battler data BattleAI_SetupAIData(0xF, battler); - SetupAISwitchingData(battler, switchType); + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES) + { + AI_DATA->predictedMove[opposingBattler] = BattleAI_PredictMove(battler, opposingBattler); + DebugPrintf("Predicted move: %d", AI_DATA->predictedMove[opposingBattler]); + } + + SetupAISwitchingData(battler, opposingBattler, switchType); // Do scoring gAiBattleData->moveOrAction[battler] = BattleAI_ChooseMoveOrAction(battler); diff --git a/test/battle/ai/ai_flag_predict_move.c b/test/battle/ai/ai_flag_predict_move.c index c918e4a55e..4e0486b6a1 100644 --- a/test/battle/ai/ai_flag_predict_move.c +++ b/test/battle/ai/ai_flag_predict_move.c @@ -5,8 +5,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_PREDICT_MOVE: AI will predict player's move") { GIVEN { - AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PREDICT_MOVE); - PLAYER(SPECIES_VAPOREON) { Moves(MOVE_SURF, MOVE_TACKLE); } + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PREDICT_MOVES); + PLAYER(SPECIES_VAPOREON) { Ability(ABILITY_WATER_ABSORB); Moves(MOVE_SURF, MOVE_TACKLE); } OPPONENT(SPECIES_NUMEL) { Moves(MOVE_TACKLE); } OPPONENT(SPECIES_VAPOREON) { Ability(ABILITY_WATER_ABSORB); Moves(MOVE_TACKLE); } } WHEN {