From d7a6e0bea88d48e14b584a93ce990dbd3eae815f Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Mon, 7 Apr 2025 23:22:01 -0400 Subject: [PATCH 01/12] Initialize move scores --- include/battle.h | 1 + include/battle_ai_main.h | 6 ++++++ include/constants/battle_ai.h | 3 ++- src/battle_ai_main.c | 21 +++++++++++++++++++-- test/battle/ai/ai_flag_predict_move.c | 15 +++++++++++++++ 5 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 test/battle/ai/ai_flag_predict_move.c diff --git a/include/battle.h b/include/battle.h index ae73365aad..a95dfc9066 100644 --- a/include/battle.h +++ b/include/battle.h @@ -360,6 +360,7 @@ struct AI_ThinkingStruct u8 movesetIndex; u16 moveConsidered; s32 score[MAX_MON_MOVES]; + s32 predictedScore[MAX_MON_MOVES][MAX_BATTLERS_COUNT]; u32 funcResult; u32 aiFlags[MAX_BATTLERS_COUNT]; u8 aiAction; diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index b21b20751d..35edd0c10e 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -64,6 +64,12 @@ typedef s32 (*AiScoreFunc)(u32, u32, u32, s32); AI_THINKING_STRUCT->score[movesetIndex] = val; \ } while (0) \ +#define SET_PREDICTED_SCORE(battler, movesetIndex, val) \ + do \ + { \ + AI_THINKING_STRUCT->predictedScore[movesetIndex][battler] = val; \ + } while (0) \ + #define ADJUST_SCORE(val) \ do \ { \ diff --git a/include/constants/battle_ai.h b/include/constants/battle_ai.h index 8ba2c72096..0dbc689844 100644 --- a/include/constants/battle_ai.h +++ b/include/constants/battle_ai.h @@ -43,13 +43,14 @@ #define AI_FLAG_PREFER_HIGHEST_DAMAGE_MOVE (1 << 22) // AI adds score to highest damage move regardless of accuracy or secondary effect #define AI_FLAG_PREDICT_SWITCH (1 << 23) // AI will predict the player's switches and switchins based on how it would handle the situation. Recommend using AI_FLAG_OMNISCIENT #define AI_FLAG_PREDICT_INCOMING_MON (1 << 24) // AI will score against the predicting incoming mon if it predicts the player to switch. Requires AI_FLAG_PREDICT_SWITCH +#define AI_FLAG_PREDICT_MOVES (1 << 25) // #define AI_FLAG_COUNT 25 // The following options are enough to have a basic/smart trainer. Any other addtion could make the trainer worse/better depending on the flag #define AI_FLAG_BASIC_TRAINER (AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY) #define AI_FLAG_SMART_TRAINER (AI_FLAG_BASIC_TRAINER | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_WEIGH_ABILITY_PREDICTION) -#define AI_FLAG_PREDICTION (AI_FLAG_PREDICT_SWITCH | AI_FLAG_PREDICT_INCOMING_MON) +#define AI_FLAG_PREDICTION (AI_FLAG_PREDICT_SWITCH | AI_FLAG_PREDICT_INCOMING_MON | AI_FLAG_PREDICT_MOVES) // 'other' ai logic flags #define AI_FLAG_DYNAMIC_FUNC (1 << 28) // Create custom AI functions for specific battles via "setdynamicaifunc" cmd diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 6c651d6639..ac474b1066 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -238,8 +238,9 @@ void BattleAI_SetupFlags(void) void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) { - u32 moveLimitations; + u32 moveLimitations, moveLimitationsTarget; u32 flags[MAX_BATTLERS_COUNT]; + u32 moveIndex; // Clear AI data but preserve the flags. memcpy(&flags[0], &AI_THINKING_STRUCT->aiFlags[0], sizeof(u32) * MAX_BATTLERS_COUNT); @@ -249,7 +250,7 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) moveLimitations = AI_DATA->moveLimitations[battler]; // Conditional score reset, unlike Ruby. - for (u32 moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) + for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) { if (moveLimitations & (1u << moveIndex)) SET_SCORE(battler, moveIndex, 0); @@ -263,6 +264,22 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) gBattlerTarget = SetRandomTarget(battler); gAiBattleData->chosenTarget[battler] = gBattlerTarget; + + // Initialize move prediction scores + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES) + { + moveLimitationsTarget = AI_DATA->moveLimitations[gBattlerTarget]; + + 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); + else + SET_PREDICTED_SCORE(gBattlerTarget, moveIndex, 0); + } + } } u32 BattleAI_ChooseMoveOrAction(u32 battler) diff --git a/test/battle/ai/ai_flag_predict_move.c b/test/battle/ai/ai_flag_predict_move.c new file mode 100644 index 0000000000..c918e4a55e --- /dev/null +++ b/test/battle/ai/ai_flag_predict_move.c @@ -0,0 +1,15 @@ +#include "global.h" +#include "test/battle.h" +#include "battle_ai_util.h" + +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); } + OPPONENT(SPECIES_NUMEL) { Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_VAPOREON) { Ability(ABILITY_WATER_ABSORB); Moves(MOVE_TACKLE); } + } WHEN { + TURN { MOVE(player, MOVE_SURF); EXPECT_SWITCH(opponent, 1); } + } +} From 11d26af7a8fc906d9720fd58c7f9efc6907d1c58 Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Tue, 8 Apr 2025 00:15:28 -0400 Subject: [PATCH 02/12] Initial attempt, doesn't see move effectiveness / damage --- include/battle.h | 1 + include/battle_ai_main.h | 1 + include/config/general.h | 2 +- src/battle_ai_main.c | 105 ++++++++++++++++++++++++-- src/battle_ai_switch_items.c | 2 +- src/battle_main.c | 13 +++- test/battle/ai/ai_flag_predict_move.c | 4 +- 7 files changed, 114 insertions(+), 14 deletions(-) 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 { From 38c90909eb17995f6cc10981dda42f170d206e72 Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Mon, 28 Apr 2025 00:56:03 -0400 Subject: [PATCH 03/12] WORKS, still needs work :D --- src/battle_ai_main.c | 8 ++++++-- src/battle_ai_switch_items.c | 2 +- src/battle_main.c | 11 ++++++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index b727a90c4e..7203742af1 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -326,8 +326,8 @@ void Ai_InitPartyStruct(void) bool32 isOmniscient = (AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_OMNISCIENT) || (AI_THINKING_STRUCT->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_OMNISCIENT); struct Pokemon *mon; - AI_PARTY->count[B_SIDE_PLAYER] = gPlayerPartyCount; - AI_PARTY->count[B_SIDE_OPPONENT] = gEnemyPartyCount; + AI_PARTY->count[B_SIDE_PLAYER] = CalculatePlayerPartyCount(); + AI_PARTY->count[B_SIDE_OPPONENT] = CalculateEnemyPartyCount(); // Save first 2 or 4(in doubles) mons CopyBattlerDataToAIParty(B_POSITION_PLAYER_LEFT, B_SIDE_PLAYER); @@ -558,6 +558,10 @@ u32 BattleAI_PredictMove(u32 battler, u32 opposingBattler) } } } + DebugPrintf("Number of best moves: %d", numOfBestMoves); + DebugPrintf("Random best move: %d", consideredMoveArray[Random() % numOfBestMoves]); + DebugPrintf("Random best move: %d", consideredMoveArray[Random() % numOfBestMoves]); + DebugPrintf("Random best move: %d", consideredMoveArray[Random() % numOfBestMoves]); return consideredMoveArray[Random() % numOfBestMoves]; } diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 7efd6a3b08..e2736a3b08 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -459,7 +459,7 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) if (!(AI_THINKING_STRUCT->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)) return FALSE; - if (gBattleStruct->prevTurnSpecies[battler] != gBattleMons[battler].species) // AI mon has changed, player's behaviour no longer reliable; note to override this if using AI_FLAG_PREDICT_MOVE + if (gBattleStruct->prevTurnSpecies[battler] != gBattleMons[battler].species && !(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES)) // AI mon has changed, player's behaviour no longer reliable; note to override this if using AI_FLAG_PREDICT_MOVE return FALSE; if (HasSuperEffectiveMoveAgainstOpponents(battler, TRUE) && (RandomPercentage(RNG_AI_SWITCH_ABSORBING_STAY_IN, STAY_IN_ABSORBING_PERCENTAGE) || AI_DATA->aiSwitchPredictionInProgress)) return FALSE; diff --git a/src/battle_main.c b/src/battle_main.c index f7c2a79406..d700b78346 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4151,6 +4151,12 @@ void SetupAISwitchingData(u32 battler, u32 opposingBattler, enum SwitchType swit AI_DATA->predictingSwitch = RandomPercentage(RNG_AI_PREDICT_SWITCH, PREDICT_SWITCH_CHANCE); } + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES) + { + AI_DATA->predictedMove[opposingBattler] = gBattleMons[opposingBattler].moves[BattleAI_PredictMove(battler, opposingBattler)]; + DebugPrintf("Predicted move: %d", AI_DATA->predictedMove[opposingBattler]); + } + // AI's data AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, switchType); if (ShouldSwitch(battler)) @@ -4182,11 +4188,6 @@ static void HandleTurnActionSelectionState(void) // Setup battler data BattleAI_SetupAIData(0xF, battler); - 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); From ab6a97d20da6078dad73efa4f69daf0194b6021d Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Mon, 28 Apr 2025 21:47:17 -0400 Subject: [PATCH 04/12] remove predictedScore --- include/battle.h | 1 - include/battle_ai_main.h | 6 ------ src/battle_ai_main.c | 26 +++++++++++++------------- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/include/battle.h b/include/battle.h index 384b44c672..588247ad00 100644 --- a/include/battle.h +++ b/include/battle.h @@ -360,7 +360,6 @@ struct AI_ThinkingStruct u8 movesetIndex; u16 moveConsidered; s32 score[MAX_MON_MOVES]; - s32 predictedScore[MAX_MON_MOVES][MAX_BATTLERS_COUNT]; u32 funcResult; u32 aiFlags[MAX_BATTLERS_COUNT]; u8 aiAction; diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index a8a2071a2b..771d2b7643 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -68,12 +68,6 @@ enum AIScore AI_THINKING_STRUCT->score[movesetIndex] = val; \ } while (0) \ -#define SET_PREDICTED_SCORE(battler, movesetIndex, val) \ - do \ - { \ - AI_THINKING_STRUCT->predictedScore[movesetIndex][battler] = val; \ - } while (0) \ - #define ADJUST_SCORE(val) \ do \ { \ diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index a539b72d0d..8713cf69a0 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -276,11 +276,11 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) { if (moveLimitationsTarget & (1u << moveIndex)) - SET_PREDICTED_SCORE(opposingBattler, moveIndex, 0); + SET_SCORE(opposingBattler, moveIndex, 0); if (defaultScoreMovesTarget & 1) - SET_PREDICTED_SCORE(opposingBattler, moveIndex, AI_SCORE_DEFAULT); + SET_SCORE(opposingBattler, moveIndex, AI_SCORE_DEFAULT); else - SET_PREDICTED_SCORE(opposingBattler, moveIndex, 0); + SET_SCORE(opposingBattler, moveIndex, 0); defaultScoreMovesTarget >>= 1; } @@ -532,12 +532,12 @@ u32 BattleAI_PredictMove(u32 battler, u32 opposingBattler) for (i = 0; i < MAX_MON_MOVES; i++) { - gAiBattleData->finalScore[opposingBattler][battler][i] = AI_THINKING_STRUCT->predictedScore[i][opposingBattler]; + gAiBattleData->finalScore[opposingBattler][battler][i] = AI_THINKING_STRUCT->score[i]; DebugPrintf("Final score: %d", gAiBattleData->finalScore[opposingBattler][battler][i]); } numOfBestMoves = 1; - currentMoveArray[0] = AI_THINKING_STRUCT->predictedScore[0][opposingBattler]; + currentMoveArray[0] = AI_THINKING_STRUCT->score[0]; consideredMoveArray[0] = 0; for (i = 1; i < MAX_MON_MOVES; i++) @@ -545,15 +545,15 @@ u32 BattleAI_PredictMove(u32 battler, u32 opposingBattler) 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]) + if (currentMoveArray[0] == AI_THINKING_STRUCT->score[i]) { - currentMoveArray[numOfBestMoves] = AI_THINKING_STRUCT->predictedScore[i][opposingBattler]; + currentMoveArray[numOfBestMoves] = AI_THINKING_STRUCT->score[i]; consideredMoveArray[numOfBestMoves++] = i; } - if (currentMoveArray[0] < AI_THINKING_STRUCT->predictedScore[i][opposingBattler]) + if (currentMoveArray[0] < AI_THINKING_STRUCT->score[i]) { numOfBestMoves = 1; - currentMoveArray[0] = AI_THINKING_STRUCT->predictedScore[i][opposingBattler]; + currentMoveArray[0] = AI_THINKING_STRUCT->score[i]; consideredMoveArray[0] = i; } } @@ -776,17 +776,17 @@ static inline void BattleAI_DoAIProcessing_PredictMove(struct AI_ThinkingStruct && sBattleAiFuncTable[aiThink->aiLogicId] != NULL) { // Call AI function - aiThink->predictedScore[aiThink->movesetIndex][opposingBattler] = + aiThink->score[aiThink->movesetIndex] = sBattleAiFuncTable[aiThink->aiLogicId](opposingBattler, battler, aiThink->moveConsidered, - aiThink->predictedScore[aiThink->movesetIndex][opposingBattler]); - DebugPrintf("Current score: %d", aiThink->predictedScore[aiThink->movesetIndex][opposingBattler]); + aiThink->score[aiThink->movesetIndex]); + DebugPrintf("Current score: %d", aiThink->score[aiThink->movesetIndex]); } } else { - aiThink->predictedScore[aiThink->movesetIndex][opposingBattler] = 0; + aiThink->score[aiThink->movesetIndex] = 0; } aiThink->movesetIndex++; } while (aiThink->movesetIndex < MAX_MON_MOVES); From 63ffe603be46adf201a8bfc20e24438a8f5903d8 Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Thu, 8 May 2025 14:04:27 -0400 Subject: [PATCH 05/12] merge fixes --- src/battle_ai_main.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index d086f5c67f..f31dfae6bd 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -324,8 +324,13 @@ void SetupAIPredictionData(u32 battler, enum SwitchType switchType) AI_DATA->predictingSwitch = RandomPercentage(RNG_AI_PREDICT_SWITCH, PREDICT_SWITCH_CHANCE); } - // TODO Move prediction - // ModifySwitchAfterMoveScoring(opposingBattler); + // Move prediction + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES) + { + AI_DATA->predictedMove[opposingBattler] = gBattleMons[opposingBattler].moves[BattleAI_PredictMove(battler, opposingBattler)]; + DebugPrintf("Predicted move: %d", AI_DATA->predictedMove[opposingBattler]); + ModifySwitchAfterMoveScoring(opposingBattler); + } } void ComputeBattlerDecisions(u32 battler) From 83b7732faed3edc3bc2cc6ee41cfc22ad3446a3f Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Thu, 8 May 2025 14:19:43 -0400 Subject: [PATCH 06/12] Store predictingMove, config for chance --- include/battle.h | 5 +++-- include/config/ai.h | 1 + include/random.h | 1 + src/battle_ai_main.c | 3 +++ src/battle_ai_switch_items.c | 18 ++++++++---------- test/battle/ai/ai_flag_predict_move.c | 1 + 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/include/battle.h b/include/battle.h index e463b94714..4fa543053b 100644 --- a/include/battle.h +++ b/include/battle.h @@ -331,9 +331,10 @@ struct AiLogicData u8 weatherHasEffect:1; // The same as HasWeatherEffect(). Stored here, so it's called only once. u8 ejectButtonSwitch:1; // Tracks whether current switch out was from Eject Button u8 ejectPackSwitch:1; // Tracks whether current switch out was from Eject Pack - u8 predictingSwitch:1; // Determines whether AI will use predictions this turn or not + u8 predictingSwitch:1; // Determines whether AI will use switch predictions this turn or not + u8 predictingMove:1; // Determines whether AI will use move predictions this turn or not u8 aiSwitchPredictionInProgress:1; // Tracks whether the AI is in the middle of running prediction calculations - u8 padding:3; + u8 padding:2; 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 diff --git a/include/config/ai.h b/include/config/ai.h index 31fca355dd..c734eb2c1d 100644 --- a/include/config/ai.h +++ b/include/config/ai.h @@ -62,6 +62,7 @@ // AI prediction chances #define PREDICT_SWITCH_CHANCE 50 +#define PREDICT_MOVE_CHANCE 100 // AI PP Stall detection chance per roll #define PP_STALL_DISREGARD_MOVE_PERCENTAGE 50 diff --git a/include/random.h b/include/random.h index d47260c7f0..b9a0ab4311 100644 --- a/include/random.h +++ b/include/random.h @@ -196,6 +196,7 @@ enum RandomTag RNG_RANDOM_TARGET, RNG_AI_PREDICT_ABILITY, RNG_AI_PREDICT_SWITCH, + RNG_AI_PREDICT_MOVE, RNG_AI_STATUS_FOCUS_PUNCH, RNG_HEALER, RNG_DEXNAV_ENCOUNTER_LEVEL, diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index f31dfae6bd..2af6e3067d 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -330,6 +330,9 @@ void SetupAIPredictionData(u32 battler, enum SwitchType switchType) AI_DATA->predictedMove[opposingBattler] = gBattleMons[opposingBattler].moves[BattleAI_PredictMove(battler, opposingBattler)]; DebugPrintf("Predicted move: %d", AI_DATA->predictedMove[opposingBattler]); ModifySwitchAfterMoveScoring(opposingBattler); + + // Determine whether AI will use predictions this turn + AI_DATA->predictingMove = RandomPercentage(RNG_AI_PREDICT_MOVE, PREDICT_MOVE_CHANCE); } } diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 37532d3055..d1a489580c 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -456,10 +456,8 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) struct Pokemon *party; u16 monAbility, aiMove; u32 opposingBattler = GetOppositeBattler(battler); - u32 incomingMove = (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES) ? AI_DATA->predictedMove[opposingBattler] : AI_DATA->lastUsedMove[opposingBattler]; + u32 incomingMove = ((AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES) && AI_DATA->predictingMove) ? AI_DATA->predictedMove[opposingBattler] : AI_DATA->lastUsedMove[opposingBattler]; u32 incomingType = GetMoveType(incomingMove); - u32 predictedMove = incomingMove; // Update for move prediction - u32 predictedType = GetMoveType(predictedMove); bool32 isOpposingBattlerChargingOrInvulnerable = (IsSemiInvulnerable(opposingBattler, incomingMove) || IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove)); s32 i, j; @@ -502,38 +500,38 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) } // Create an array of possible absorb abilities so the AI considers all of them - if (predictedType == TYPE_FIRE) + if (incomingType == TYPE_FIRE) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_FLASH_FIRE; } - else if (predictedType == TYPE_WATER || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_WATER)) + else if (incomingType == TYPE_WATER || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_WATER)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_WATER_ABSORB; absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_DRY_SKIN; if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_STORM_DRAIN; } - else if (predictedType == TYPE_ELECTRIC || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_ELECTRIC)) + else if (incomingType == TYPE_ELECTRIC || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_ELECTRIC)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_VOLT_ABSORB; absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_MOTOR_DRIVE; if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_LIGHTNING_ROD; } - else if (predictedType == TYPE_GRASS || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GRASS)) + else if (incomingType == TYPE_GRASS || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GRASS)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_SAP_SIPPER; } - else if (predictedType == TYPE_GROUND || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GROUND)) + else if (incomingType == TYPE_GROUND || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GROUND)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_EARTH_EATER; absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_LEVITATE; } - else if (IsSoundMove(predictedMove) || (isOpposingBattlerChargingOrInvulnerable && IsSoundMove(incomingMove))) + else if (IsSoundMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsSoundMove(incomingMove))) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_SOUNDPROOF; } - else if (IsWindMove(predictedMove) || (isOpposingBattlerChargingOrInvulnerable && IsWindMove(incomingMove))) + else if (IsWindMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsWindMove(incomingMove))) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_WIND_RIDER; } diff --git a/test/battle/ai/ai_flag_predict_move.c b/test/battle/ai/ai_flag_predict_move.c index 4e0486b6a1..80c5d81c16 100644 --- a/test/battle/ai/ai_flag_predict_move.c +++ b/test/battle/ai/ai_flag_predict_move.c @@ -4,6 +4,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_PREDICT_MOVE: AI will predict player's move") { + PASSES_RANDOMLY(PREDICT_MOVE_CHANCE, 100, RNG_AI_PREDICT_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_MOVES); PLAYER(SPECIES_VAPOREON) { Ability(ABILITY_WATER_ABSORB); Moves(MOVE_SURF, MOVE_TACKLE); } From b48d0c387d026097b227633dc857287c8170acfd Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Thu, 8 May 2025 14:49:49 -0400 Subject: [PATCH 07/12] Throw out unnecessary functions --- include/battle.h | 2 +- include/battle_ai_util.h | 1 + src/battle_ai_main.c | 163 ++++++++--------------------------- src/battle_ai_switch_items.c | 21 ++--- src/battle_ai_util.c | 7 ++ 5 files changed, 51 insertions(+), 143 deletions(-) diff --git a/include/battle.h b/include/battle.h index 4fa543053b..56ea4f254b 100644 --- a/include/battle.h +++ b/include/battle.h @@ -333,7 +333,7 @@ struct AiLogicData u8 ejectPackSwitch:1; // Tracks whether current switch out was from Eject Pack u8 predictingSwitch:1; // Determines whether AI will use switch predictions this turn or not u8 predictingMove:1; // Determines whether AI will use move predictions this turn or not - u8 aiSwitchPredictionInProgress:1; // Tracks whether the AI is in the middle of running prediction calculations + u8 aiPredictionInProgress:1; // Tracks whether the AI is in the middle of running prediction calculations u8 padding:2; u8 shouldSwitch; // Stores result of ShouldSwitch, which decides whether a mon should be switched out u8 aiCalcInProgress:1; diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 17adb67712..ca3f7c1d1e 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -240,5 +240,6 @@ bool32 IsBattlerItemEnabled(u32 battler); bool32 IsBattlerPredictedToSwitch(u32 battler); bool32 HasLowAccuracyMove(u32 battlerAtk, u32 battlerDef); bool32 HasBattlerSideAbility(u32 battlerDef, u32 ability, struct AiLogicData *aiData); +u32 GetThinkingBattler(u32 battler); #endif //GUARD_BATTLE_AI_UTIL_H diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 2af6e3067d..f07ed8a03b 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -32,11 +32,10 @@ #define AI_ACTION_WATCH (1 << 2) #define AI_ACTION_DO_NOT_ATTACK (1 << 3) -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 u32 ChooseMoveOrAction_Singles(u32 battler); +static u32 ChooseMoveOrAction_Doubles(u32 battler); +static inline void BattleAI_DoAIProcessing(struct AI_ThinkingStruct *aiThink, u32 battler, u32 battlerDef); +static inline void BattleAI_DoAIProcessing_PredictedSwitchin(struct AI_ThinkingStruct *aiThink, struct AiLogicData *aiData, u32 battlerAtk, u32 battlerDef); static bool32 IsPinchBerryItemEffect(enum ItemHoldEffect holdEffect); // ewram @@ -308,16 +307,15 @@ bool32 BattlerChoseNonMoveAction(void) void SetupAIPredictionData(u32 battler, enum SwitchType switchType) { s32 opposingBattler = GetOppositeBattler(battler); - + AI_DATA->aiPredictionInProgress = TRUE; + AI_DATA->battlerDoingPrediction = battler; + // Switch prediction if ((AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_SWITCH)) { - AI_DATA->aiSwitchPredictionInProgress = TRUE; - AI_DATA->battlerDoingPrediction = battler; AI_DATA->mostSuitableMonId[opposingBattler] = GetMostSuitableMonToSwitchInto(opposingBattler, switchType); if (ShouldSwitch(opposingBattler)) AI_DATA->shouldSwitch |= (1u << opposingBattler); - AI_DATA->aiSwitchPredictionInProgress = FALSE; gBattleStruct->prevTurnSpecies[opposingBattler] = gBattleMons[opposingBattler].species; // Determine whether AI will use predictions this turn @@ -327,13 +325,14 @@ void SetupAIPredictionData(u32 battler, enum SwitchType switchType) // Move prediction if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES) { - AI_DATA->predictedMove[opposingBattler] = gBattleMons[opposingBattler].moves[BattleAI_PredictMove(battler, opposingBattler)]; + AI_DATA->predictedMove[opposingBattler] = gBattleMons[opposingBattler].moves[BattleAI_ChooseMoveIndex(opposingBattler)]; DebugPrintf("Predicted move: %d", AI_DATA->predictedMove[opposingBattler]); ModifySwitchAfterMoveScoring(opposingBattler); // Determine whether AI will use predictions this turn AI_DATA->predictingMove = RandomPercentage(RNG_AI_PREDICT_MOVE, PREDICT_MOVE_CHANCE); } + AI_DATA->aiPredictionInProgress = FALSE; } void ComputeBattlerDecisions(u32 battler) @@ -640,77 +639,23 @@ static u32 PpStallReduction(u32 move, u32 battlerAtk) return returnValue; } -u32 BattleAI_PredictMove(u32 battler, u32 opposingBattler) +static u32 ChooseMoveOrAction_Singles(u32 battler) { u8 currentMoveArray[MAX_MON_MOVES]; u8 consideredMoveArray[MAX_MON_MOVES]; u32 numOfBestMoves; s32 i; - u32 flags = AI_THINKING_STRUCT->aiFlags[battler]; + u64 flags = AI_THINKING_STRUCT->aiFlags[GetThinkingBattler(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->score[i]; - DebugPrintf("Final score: %d", gAiBattleData->finalScore[opposingBattler][battler][i]); - } - - numOfBestMoves = 1; - currentMoveArray[0] = AI_THINKING_STRUCT->score[0]; - 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->score[i]) - { - currentMoveArray[numOfBestMoves] = AI_THINKING_STRUCT->score[i]; - consideredMoveArray[numOfBestMoves++] = i; - } - if (currentMoveArray[0] < AI_THINKING_STRUCT->score[i]) - { - numOfBestMoves = 1; - currentMoveArray[0] = AI_THINKING_STRUCT->score[i]; - consideredMoveArray[0] = i; - } - } - } - DebugPrintf("Number of best moves: %d", numOfBestMoves); - DebugPrintf("Random best move: %d", consideredMoveArray[Random() % numOfBestMoves]); - DebugPrintf("Random best move: %d", consideredMoveArray[Random() % numOfBestMoves]); - DebugPrintf("Random best move: %d", consideredMoveArray[Random() % numOfBestMoves]); - return consideredMoveArray[Random() % numOfBestMoves]; -} - -static u32 ChooseMoveOrAction_Singles(u32 battlerAi) -{ - u8 currentMoveArray[MAX_MON_MOVES]; - u8 consideredMoveArray[MAX_MON_MOVES]; - u32 numOfBestMoves; - s32 i; - u64 flags = AI_THINKING_STRUCT->aiFlags[battlerAi]; - - AI_DATA->partnerMove = 0; // no ally - while (flags != 0) - { - if (flags & 1) - { - if (IsBattlerPredictedToSwitch(gBattlerTarget) && (AI_THINKING_STRUCT->aiFlags[battlerAi] & AI_FLAG_PREDICT_INCOMING_MON)) - BattleAI_DoAIProcessing_PredictedSwitchin(AI_THINKING_STRUCT, AI_DATA, battlerAi, gBattlerTarget); + if (IsBattlerPredictedToSwitch(gBattlerTarget) && (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_INCOMING_MON)) + BattleAI_DoAIProcessing_PredictedSwitchin(AI_THINKING_STRUCT, AI_DATA, battler, gBattlerTarget); else - BattleAI_DoAIProcessing(AI_THINKING_STRUCT, battlerAi, gBattlerTarget); + BattleAI_DoAIProcessing(AI_THINKING_STRUCT, battler, gBattlerTarget); } flags >>= (u64)1; AI_THINKING_STRUCT->aiLogicId++; @@ -718,7 +663,7 @@ static u32 ChooseMoveOrAction_Singles(u32 battlerAi) for (i = 0; i < MAX_MON_MOVES; i++) { - gAiBattleData->finalScore[battlerAi][gBattlerTarget][i] = AI_THINKING_STRUCT->score[i]; + gAiBattleData->finalScore[battler][gBattlerTarget][i] = AI_THINKING_STRUCT->score[i]; } numOfBestMoves = 1; @@ -727,7 +672,7 @@ static u32 ChooseMoveOrAction_Singles(u32 battlerAi) for (i = 1; i < MAX_MON_MOVES; i++) { - if (gBattleMons[battlerAi].moves[i] != MOVE_NONE) + if (gBattleMons[battler].moves[i] != MOVE_NONE) { // In ruby, the order of these if statements is reversed. if (currentMoveArray[0] == AI_THINKING_STRUCT->score[i]) @@ -746,7 +691,7 @@ static u32 ChooseMoveOrAction_Singles(u32 battlerAi) return consideredMoveArray[Random() % numOfBestMoves]; } -static u32 ChooseMoveOrAction_Doubles(u32 battlerAi) +static u32 ChooseMoveOrAction_Doubles(u32 battler) { s32 i, j; u64 flags; @@ -761,7 +706,7 @@ static u32 ChooseMoveOrAction_Doubles(u32 battlerAi) for (i = 0; i < MAX_BATTLERS_COUNT; i++) { - if (i == battlerAi || gBattleMons[i].hp == 0) + if (i == battler || gBattleMons[i].hp == 0) { actionOrMoveIndex[i] = 0xFF; bestMovePointsForTarget[i] = -1; @@ -769,25 +714,25 @@ static u32 ChooseMoveOrAction_Doubles(u32 battlerAi) else { if (gBattleTypeFlags & BATTLE_TYPE_PALACE) - BattleAI_SetupAIData(gBattleStruct->palaceFlags >> 4, battlerAi); + BattleAI_SetupAIData(gBattleStruct->palaceFlags >> 4, battler); else - BattleAI_SetupAIData(0xF, battlerAi); + BattleAI_SetupAIData(0xF, battler); gBattlerTarget = i; - AI_DATA->partnerMove = GetAllyChosenMove(battlerAi); + AI_DATA->partnerMove = GetAllyChosenMove(battler); AI_THINKING_STRUCT->aiLogicId = 0; AI_THINKING_STRUCT->movesetIndex = 0; - flags = AI_THINKING_STRUCT->aiFlags[battlerAi]; + flags = AI_THINKING_STRUCT->aiFlags[GetThinkingBattler(battler)]; while (flags != 0) { if (flags & 1) { - if (IsBattlerPredictedToSwitch(gBattlerTarget) && (AI_THINKING_STRUCT->aiFlags[battlerAi] & AI_FLAG_PREDICT_INCOMING_MON)) - BattleAI_DoAIProcessing_PredictedSwitchin(AI_THINKING_STRUCT, AI_DATA, battlerAi, gBattlerTarget); + if (IsBattlerPredictedToSwitch(gBattlerTarget) && (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_INCOMING_MON)) + BattleAI_DoAIProcessing_PredictedSwitchin(AI_THINKING_STRUCT, AI_DATA, battler, gBattlerTarget); else - BattleAI_DoAIProcessing(AI_THINKING_STRUCT, battlerAi, gBattlerTarget); + BattleAI_DoAIProcessing(AI_THINKING_STRUCT, battler, gBattlerTarget); } flags >>= (u64)1; AI_THINKING_STRUCT->aiLogicId++; @@ -798,9 +743,9 @@ static u32 ChooseMoveOrAction_Doubles(u32 battlerAi) mostViableMovesNo = 1; for (j = 1; j < MAX_MON_MOVES; j++) { - if (gBattleMons[battlerAi].moves[j] != 0) + if (gBattleMons[battler].moves[j] != 0) { - if (!CanTargetBattler(battlerAi, i, gBattleMons[battlerAi].moves[j])) + if (!CanTargetBattler(battler, i, gBattleMons[battler].moves[j])) continue; if (mostViableMovesScores[0] == AI_THINKING_STRUCT->score[j]) @@ -821,14 +766,14 @@ static u32 ChooseMoveOrAction_Doubles(u32 battlerAi) bestMovePointsForTarget[i] = mostViableMovesScores[0]; // Don't use a move against ally if it has less than 100 points. - if (i == BATTLE_PARTNER(battlerAi) && bestMovePointsForTarget[i] < AI_SCORE_DEFAULT) + if (i == BATTLE_PARTNER(battler) && bestMovePointsForTarget[i] < AI_SCORE_DEFAULT) { bestMovePointsForTarget[i] = -1; } for (j = 0; j < MAX_MON_MOVES; j++) { - gAiBattleData->finalScore[battlerAi][gBattlerTarget][j] = AI_THINKING_STRUCT->score[j]; + gAiBattleData->finalScore[battler][gBattlerTarget][j] = AI_THINKING_STRUCT->score[j]; } } } @@ -853,7 +798,7 @@ static u32 ChooseMoveOrAction_Doubles(u32 battlerAi) } gBattlerTarget = mostViableTargetsArray[Random() % mostViableTargetsNo]; - gAiBattleData->chosenTarget[battlerAi] = gBattlerTarget; + gAiBattleData->chosenTarget[battler] = gBattlerTarget; return actionOrMoveIndex[gBattlerTarget]; } @@ -868,64 +813,26 @@ static inline bool32 ShouldConsiderMoveForBattler(u32 battlerAi, u32 battlerDef, return TRUE; } -static inline void BattleAI_DoAIProcessing_PredictMove(struct AI_ThinkingStruct *aiThink, u32 battler, u32 opposingBattler) +static inline void BattleAI_DoAIProcessing(struct AI_ThinkingStruct *aiThink, u32 battler, u32 battlerDef) { do { - if (gBattleMons[opposingBattler].pp[aiThink->movesetIndex] == 0) + if (gBattleMons[battler].pp[aiThink->movesetIndex] == 0) aiThink->moveConsidered = MOVE_NONE; else - aiThink->moveConsidered = gBattleMons[opposingBattler].moves[aiThink->movesetIndex]; - - DebugPrintf("Move considered: %d", aiThink->moveConsidered); + aiThink->moveConsidered = gBattleMons[battler].moves[aiThink->movesetIndex]; // 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)) + && ShouldConsiderMoveForBattler(battler, battlerDef, aiThink->moveConsidered)) { if (aiThink->aiLogicId < ARRAY_COUNT(sBattleAiFuncTable) && sBattleAiFuncTable[aiThink->aiLogicId] != NULL) { // Call AI function aiThink->score[aiThink->movesetIndex] = - sBattleAiFuncTable[aiThink->aiLogicId](opposingBattler, - battler, - aiThink->moveConsidered, - aiThink->score[aiThink->movesetIndex]); - DebugPrintf("Current score: %d", aiThink->score[aiThink->movesetIndex]); - } - } - else - { - aiThink->score[aiThink->movesetIndex] = 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 - { - if (gBattleMons[battlerAi].pp[aiThink->movesetIndex] == 0) - aiThink->moveConsidered = MOVE_NONE; - else - aiThink->moveConsidered = gBattleMons[battlerAi].moves[aiThink->movesetIndex]; - - // 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(battlerAi, battlerDef, aiThink->moveConsidered)) - { - if (aiThink->aiLogicId < ARRAY_COUNT(sBattleAiFuncTable) - && sBattleAiFuncTable[aiThink->aiLogicId] != NULL) - { - // Call AI function - aiThink->score[aiThink->movesetIndex] = - sBattleAiFuncTable[aiThink->aiLogicId](battlerAi, + sBattleAiFuncTable[aiThink->aiLogicId](battler, battlerDef, aiThink->moveConsidered, aiThink->score[aiThink->movesetIndex]); diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index d1a489580c..56e6482fd1 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -108,13 +108,6 @@ u32 GetSwitchChance(enum ShouldSwitchScenario shouldSwitchScenario) } } -u32 GetThinkingBattler(u32 battler) -{ - if (AI_DATA->aiSwitchPredictionInProgress) - return AI_DATA->battlerDoingPrediction; - return battler; -} - static bool32 IsAceMon(u32 battler, u32 monPartyId) { if (AI_THINKING_STRUCT->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_ACE_POKEMON @@ -295,7 +288,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4))) { // 50% chance to stay in regardless - if (RandomPercentage(RNG_AI_SWITCH_HASBADODDS, (100 - GetSwitchChance(SHOULD_SWITCH_HASBADODDS))) && !AI_DATA->aiSwitchPredictionInProgress) + if (RandomPercentage(RNG_AI_SWITCH_HASBADODDS, (100 - GetSwitchChance(SHOULD_SWITCH_HASBADODDS))) && !AI_DATA->aiPredictionInProgress) return FALSE; // Switch mon out @@ -315,7 +308,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) return FALSE; // 50% chance to stay in regardless - if (RandomPercentage(RNG_AI_SWITCH_HASBADODDS, (100 - GetSwitchChance(SHOULD_SWITCH_HASBADODDS))) && !AI_DATA->aiSwitchPredictionInProgress) + if (RandomPercentage(RNG_AI_SWITCH_HASBADODDS, (100 - GetSwitchChance(SHOULD_SWITCH_HASBADODDS))) && !AI_DATA->aiPredictionInProgress) return FALSE; // Switch mon out @@ -465,7 +458,7 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) return FALSE; if (gBattleStruct->prevTurnSpecies[battler] != gBattleMons[battler].species && !(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES)) // AI mon has changed, player's behaviour no longer reliable; note to override this if using AI_FLAG_PREDICT_MOVE return FALSE; - if (HasSuperEffectiveMoveAgainstOpponents(battler, TRUE) && (RandomPercentage(RNG_AI_SWITCH_ABSORBING_STAY_IN, STAY_IN_ABSORBING_PERCENTAGE) || AI_DATA->aiSwitchPredictionInProgress)) + if (HasSuperEffectiveMoveAgainstOpponents(battler, TRUE) && (RandomPercentage(RNG_AI_SWITCH_ABSORBING_STAY_IN, STAY_IN_ABSORBING_PERCENTAGE) || AI_DATA->aiPredictionInProgress)) return FALSE; if (AreStatsRaised(battler)) return FALSE; @@ -898,7 +891,7 @@ static bool32 FindMonWithFlagsAndSuperEffective(u32 battler, u16 flags, u32 perc if (move == 0) continue; - if (AI_GetMoveEffectiveness(move, battler, battlerIn1) >= UQ_4_12(2.0) && (RandomPercentage(RNG_AI_SWITCH_SE_DEFENSIVE, percentChance) || AI_DATA->aiSwitchPredictionInProgress)) + if (AI_GetMoveEffectiveness(move, battler, battlerIn1) >= UQ_4_12(2.0) && (RandomPercentage(RNG_AI_SWITCH_SE_DEFENSIVE, percentChance) || AI_DATA->aiPredictionInProgress)) return SetSwitchinAndSwitch(battler, i); } } @@ -990,7 +983,7 @@ static bool32 ShouldSwitchIfEncored(u32 battler) return FALSE; // Switch out 50% of the time otherwise - else if ((RandomPercentage(RNG_AI_SWITCH_ENCORE, GetSwitchChance(SHOULD_SWITCH_ENCORE_DAMAGE)) || AI_DATA->aiSwitchPredictionInProgress) && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE) + else if ((RandomPercentage(RNG_AI_SWITCH_ENCORE, GetSwitchChance(SHOULD_SWITCH_ENCORE_DAMAGE)) || AI_DATA->aiPredictionInProgress) && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE) return SetSwitchinAndSwitch(battler, PARTY_SIZE); return FALSE; @@ -1036,7 +1029,7 @@ static bool32 ShouldSwitchIfAttackingStatsLowered(u32 battler) // 50% chance if attack at -2 and have a good candidate mon else if (attackingStage == DEFAULT_STAT_STAGE - 2) { - if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (RandomPercentage(RNG_AI_SWITCH_STATS_LOWERED, GetSwitchChance(SHOULD_SWITCH_ATTACKING_STAT_MINUS_TWO)) || AI_DATA->aiSwitchPredictionInProgress)) + if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (RandomPercentage(RNG_AI_SWITCH_STATS_LOWERED, GetSwitchChance(SHOULD_SWITCH_ATTACKING_STAT_MINUS_TWO)) || AI_DATA->aiPredictionInProgress)) return SetSwitchinAndSwitch(battler, PARTY_SIZE); } // If at -3 or worse, switch out regardless @@ -1053,7 +1046,7 @@ static bool32 ShouldSwitchIfAttackingStatsLowered(u32 battler) // 50% chance if attack at -2 and have a good candidate mon else if (spAttackingStage == DEFAULT_STAT_STAGE - 2) { - if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (RandomPercentage(RNG_AI_SWITCH_STATS_LOWERED, GetSwitchChance(SHOULD_SWITCH_ATTACKING_STAT_MINUS_TWO)) || AI_DATA->aiSwitchPredictionInProgress)) + if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (RandomPercentage(RNG_AI_SWITCH_STATS_LOWERED, GetSwitchChance(SHOULD_SWITCH_ATTACKING_STAT_MINUS_TWO)) || AI_DATA->aiPredictionInProgress)) return SetSwitchinAndSwitch(battler, PARTY_SIZE); } // If at -3 or worse, switch out regardless diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 74f0646ed3..55223e2353 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -4517,3 +4517,10 @@ bool32 HasBattlerSideAbility(u32 battler, u32 ability, struct AiLogicData *aiDat return TRUE; return FALSE; } + +u32 GetThinkingBattler(u32 battler) +{ + if (AI_DATA->aiPredictionInProgress) + return AI_DATA->battlerDoingPrediction; + return battler; +} From 04278dbfac9b1ffc8d9c47e9affd341236e008fc Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Thu, 8 May 2025 14:52:13 -0400 Subject: [PATCH 08/12] don't need prints any more --- include/battle_ai_main.h | 1 - include/config/general.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index c90a4fae6a..a385d7ce21 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -116,6 +116,5 @@ 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 323b94d314..1bb70ce00d 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). From c7ee329433e9821a42e764d87e0a3304b10e160e Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Thu, 8 May 2025 16:45:10 -0400 Subject: [PATCH 09/12] Full functionality --- docs/tutorials/ai_flags.md | 3 +++ include/constants/battle_ai.h | 6 +++--- src/battle_ai_main.c | 19 ++++++++++--------- src/battle_ai_switch_items.c | 4 ++-- src/battle_ai_util.c | 2 +- test/battle/ai/ai_flag_predict_move.c | 2 +- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/docs/tutorials/ai_flags.md b/docs/tutorials/ai_flags.md index b842f7cee4..b0ecc8d77a 100644 --- a/docs/tutorials/ai_flags.md +++ b/docs/tutorials/ai_flags.md @@ -180,5 +180,8 @@ AI will determine whether it would switch out in the player's situation or not, ## `AI_FLAG_PREDICT_INCOMING_MON` This flag requires `AI_FLAG_PREDICT_SWITCH` to function. If the AI predicts that the player will switch, this flag allows the AI to run its move scoring calculation against the Pokémon it expects the player to switch into, instead of the Pokémon that it expects to switch out. +## `AI_FLAG_PREDICT_MOVE` +AI will predict what move the player is going to use based on what move it would use in the same situation. Generally works best if also using `AI_FLAG_OMNISCIENT`. + ## `AI_FLAG_PP_STALL_PREVENTION` This flag aims to prevent the player from PP stalling the AI by switching between immunities. The AI mon's move scores will slowly decay for absorbed moves over time, eventually making its moves unpredictable. More detailed control for this behaviour can be customized in the `ai.h` config file. diff --git a/include/constants/battle_ai.h b/include/constants/battle_ai.h index 556a7acf4b..d7b70af874 100644 --- a/include/constants/battle_ai.h +++ b/include/constants/battle_ai.h @@ -31,7 +31,7 @@ #define AI_FLAG_PREDICT_SWITCH (1 << 23) // AI will predict the player's switches and switchins based on how it would handle the situation. Recommend using AI_FLAG_OMNISCIENT #define AI_FLAG_PREDICT_INCOMING_MON (1 << 24) // AI will score against the predicting incoming mon if it predicts the player to switch. Requires AI_FLAG_PREDICT_SWITCH #define AI_FLAG_PP_STALL_PREVENTION (1 << 25) // AI keeps track of the player's switches where the incoming mon is immune to the chosen move -#define AI_FLAG_PREDICT_MOVES (1 << 26) // +#define AI_FLAG_PREDICT_MOVE (1 << 26) // AI will predict the player's move based on what move it would use in the same situation. Recommend using AI_FLAG_OMNISCIENT // Flags at and after 32 need different formatting, as in // #define AI_FLAG_PLACEHOLDER ((u64)1 << 32) @@ -41,8 +41,8 @@ // The following options are enough to have a basic/smart trainer. Any other addtion could make the trainer worse/better depending on the flag #define AI_FLAG_BASIC_TRAINER (AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY) -#define AI_FLAG_SMART_TRAINER (AI_FLAG_BASIC_TRAINER | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_WEIGH_ABILITY_PREDICTION | AI_FLAG_PREDICT_MOVES) -#define AI_FLAG_PREDICTION (AI_FLAG_PREDICT_SWITCH | AI_FLAG_PREDICT_INCOMING_MON) +#define AI_FLAG_SMART_TRAINER (AI_FLAG_BASIC_TRAINER | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_WEIGH_ABILITY_PREDICTION) +#define AI_FLAG_PREDICTION (AI_FLAG_PREDICT_SWITCH | AI_FLAG_PREDICT_INCOMING_MON | AI_FLAG_PREDICT_MOVE) // 'other' ai logic flags #define AI_FLAG_DYNAMIC_FUNC ((u64)1 << 60) // Create custom AI functions for specific battles via "setdynamicaifunc" cmd diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index f07ed8a03b..f830614316 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -268,7 +268,7 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) gAiBattleData->chosenTarget[battler] = gBattlerTarget; // Initialize move prediction scores - if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES) + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVE) { u32 opposingBattler = GetOppositeBattler(battler); moveLimitationsTarget = AI_DATA->moveLimitations[opposingBattler]; @@ -323,7 +323,7 @@ void SetupAIPredictionData(u32 battler, enum SwitchType switchType) } // Move prediction - if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES) + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVE) { AI_DATA->predictedMove[opposingBattler] = gBattleMons[opposingBattler].moves[BattleAI_ChooseMoveIndex(opposingBattler)]; DebugPrintf("Predicted move: %d", AI_DATA->predictedMove[opposingBattler]); @@ -971,7 +971,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk); u32 i; u32 weather; - u32 predictedMove = aiData->lastUsedMove[battlerDef]; + u32 predictedMove = ((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_PREDICT_MOVE) && aiData->predictingMove) ? AI_DATA->predictedMove[battlerDef] : aiData->lastUsedMove[battlerDef]; u32 abilityAtk = aiData->abilities[battlerAtk]; u32 abilityDef = aiData->abilities[battlerDef]; s32 atkPriority = GetBattleMovePriority(battlerAtk, abilityAtk, move); @@ -994,8 +994,8 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (gBattleStruct->battlerState[battlerDef].commandingDondozo) RETURN_SCORE_MINUS(20); - // Don't setup into expected Focus Punch. Revisit alongside predictedMove with move prediction - if (GetMoveCategory(move) == DAMAGE_CATEGORY_STATUS && moveEffect != EFFECT_SLEEP + // Don't setup into expected Focus Punch. + if (GetMoveCategory(move) == DAMAGE_CATEGORY_STATUS && moveEffect != EFFECT_SLEEP && GetMoveEffect(predictedMove) != EFFECT_FOCUS_PUNCH && GetMoveEffect(GetBestDmgMoveFromBattler(battlerDef, battlerAtk, AI_DEFENDING)) == EFFECT_FOCUS_PUNCH && RandomPercentage(RNG_AI_STATUS_FOCUS_PUNCH, STATUS_MOVE_FOCUS_PUNCH_CHANCE)) { RETURN_SCORE_MINUS(20); @@ -2900,7 +2900,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) bool32 partnerProtecting = (partnerEffect == EFFECT_PROTECT); bool32 attackerHasBadAbility = (gAbilitiesInfo[aiData->abilities[battlerAtk]].aiRating < 0); bool32 partnerHasBadAbility = (gAbilitiesInfo[atkPartnerAbility].aiRating < 0); - u32 predictedMove = aiData->lastUsedMove[battlerDef]; + u32 predictedMove = ((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_PREDICT_MOVE) && aiData->predictingMove) ? AI_DATA->predictedMove[battlerDef] : aiData->lastUsedMove[battlerDef]; SetTypeBeforeUsingMove(move, battlerAtk); moveType = GetBattleMoveType(move); @@ -3471,7 +3471,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) uq4_12_t effectiveness = aiData->effectiveness[battlerAtk][battlerDef][movesetIndex]; s32 score = 0; - u32 predictedMove = aiData->lastUsedMove[battlerDef]; + u32 predictedMove = ((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_PREDICT_MOVE) && aiData->predictingMove) ? AI_DATA->predictedMove[battlerDef] : aiData->lastUsedMove[battlerDef]; u32 predictedType = GetMoveType(predictedMove); u32 predictedMoveSlot = GetMoveSlot(GetMovesArray(battlerDef), predictedMove); bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk); @@ -5579,6 +5579,7 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) enum BattleMoveEffects moveEffect = GetMoveEffect(move); struct AiLogicData *aiData = AI_DATA; uq4_12_t effectiveness = aiData->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]; + u32 predictedMove = ((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_PREDICT_MOVE) && aiData->predictingMove) ? AI_DATA->predictedMove[battlerDef] : aiData->lastUsedMove[battlerDef]; // Switch benefit switch (moveEffect) @@ -5590,8 +5591,8 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(GOOD_EFFECT); else if (hitsToKO == 1) ADJUST_SCORE(BEST_EFFECT); - // else if (IsPredictedToUsePursuitableMove(battlerDef, battlerAtk) && !MoveWouldHitFirst(move, battlerAtk, battlerDef)) //Pursuit against fast U-Turn - // ADJUST_SCORE(GOOD_EFFECT); + else if (IsSwitchOutEffect(GetMoveEffect(predictedMove)) && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER) // Pursuit against fast U-Turn + ADJUST_SCORE(DECENT_EFFECT); break; } diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 56e6482fd1..94b6ee1c1e 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -449,14 +449,14 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) struct Pokemon *party; u16 monAbility, aiMove; u32 opposingBattler = GetOppositeBattler(battler); - u32 incomingMove = ((AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES) && AI_DATA->predictingMove) ? AI_DATA->predictedMove[opposingBattler] : AI_DATA->lastUsedMove[opposingBattler]; + u32 incomingMove = ((AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVE) && AI_DATA->predictingMove) ? AI_DATA->predictedMove[opposingBattler] : AI_DATA->lastUsedMove[opposingBattler]; u32 incomingType = GetMoveType(incomingMove); bool32 isOpposingBattlerChargingOrInvulnerable = (IsSemiInvulnerable(opposingBattler, incomingMove) || IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove)); s32 i, j; if (!(AI_THINKING_STRUCT->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)) return FALSE; - if (gBattleStruct->prevTurnSpecies[battler] != gBattleMons[battler].species && !(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVES)) // AI mon has changed, player's behaviour no longer reliable; note to override this if using AI_FLAG_PREDICT_MOVE + if (gBattleStruct->prevTurnSpecies[battler] != gBattleMons[battler].species && !(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVE)) // AI mon has changed, player's behaviour no longer reliable; override this if using AI_FLAG_PREDICT_MOVE return FALSE; if (HasSuperEffectiveMoveAgainstOpponents(battler, TRUE) && (RandomPercentage(RNG_AI_SWITCH_ABSORBING_STAY_IN, STAY_IN_ABSORBING_PERCENTAGE) || AI_DATA->aiPredictionInProgress)) return FALSE; diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 55223e2353..a19addedc0 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -1135,7 +1135,7 @@ s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 moveConsidered) u32 abilityAI = AI_DATA->abilities[battlerAI]; u32 abilityPlayer = AI_DATA->abilities[battler]; - u32 predictedMove = AI_DATA->lastUsedMove[battler]; // TODO update for move prediction + u32 predictedMove = ((AI_THINKING_STRUCT->aiFlags[battlerAI] & AI_FLAG_PREDICT_MOVE) && AI_DATA->predictingMove) ? AI_DATA->predictedMove[battler] : AI_DATA->lastUsedMove[battler]; s8 aiPriority = GetBattleMovePriority(battlerAI, abilityAI, moveConsidered); s8 playerPriority = GetBattleMovePriority(battler, abilityPlayer, predictedMove); diff --git a/test/battle/ai/ai_flag_predict_move.c b/test/battle/ai/ai_flag_predict_move.c index 80c5d81c16..362783f731 100644 --- a/test/battle/ai/ai_flag_predict_move.c +++ b/test/battle/ai/ai_flag_predict_move.c @@ -6,7 +6,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_PREDICT_MOVE: AI will predict player's move") { PASSES_RANDOMLY(PREDICT_MOVE_CHANCE, 100, RNG_AI_PREDICT_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_MOVES); + 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) { 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); } From 92fe60269485e4e6e5ac101f2f610683217306ed Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Thu, 8 May 2025 16:48:03 -0400 Subject: [PATCH 10/12] missed one --- src/battle_ai_switch_items.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 94b6ee1c1e..2f7944877b 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -574,7 +574,7 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) static bool32 ShouldSwitchIfOpponentChargingOrInvulnerable(u32 battler) { u32 opposingBattler = GetOppositeBattler(battler); - u32 incomingMove = AI_DATA->lastUsedMove[opposingBattler]; + u32 incomingMove = ((AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_PREDICT_MOVE) && AI_DATA->predictingMove) ? AI_DATA->predictedMove[opposingBattler] : AI_DATA->lastUsedMove[opposingBattler]; bool32 isOpposingBattlerChargingOrInvulnerable = (IsSemiInvulnerable(opposingBattler, incomingMove) || IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove)); if (IsDoubleBattle() || !(AI_THINKING_STRUCT->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)) From 169d22b12cebaf0f19de20b46960ee04bb5b41e3 Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Mon, 12 May 2025 13:42:01 -0400 Subject: [PATCH 11/12] Update src/battle_ai_main.c Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- src/battle_ai_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index f830614316..c55fb44189 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -240,7 +240,7 @@ void BattleAI_SetupFlags(void) void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) { u32 moveLimitations, moveLimitationsTarget; - u8 defaultScoreMovesTarget = defaultScoreMoves; + u32 defaultScoreMovesTarget = defaultScoreMoves; u64 flags[MAX_BATTLERS_COUNT]; u32 moveIndex; From 60666b1b13fd3c0e16cacbe5e81c183416e499b9 Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Mon, 12 May 2025 13:45:36 -0400 Subject: [PATCH 12/12] rename to battlerAtk --- src/battle_ai_main.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index c55fb44189..beeab0b06a 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -34,7 +34,7 @@ static u32 ChooseMoveOrAction_Singles(u32 battler); static u32 ChooseMoveOrAction_Doubles(u32 battler); -static inline void BattleAI_DoAIProcessing(struct AI_ThinkingStruct *aiThink, u32 battler, u32 battlerDef); +static inline void BattleAI_DoAIProcessing(struct AI_ThinkingStruct *aiThink, u32 battlerAtk, u32 battlerDef); static inline void BattleAI_DoAIProcessing_PredictedSwitchin(struct AI_ThinkingStruct *aiThink, struct AiLogicData *aiData, u32 battlerAtk, u32 battlerDef); static bool32 IsPinchBerryItemEffect(enum ItemHoldEffect holdEffect); @@ -813,26 +813,26 @@ static inline bool32 ShouldConsiderMoveForBattler(u32 battlerAi, u32 battlerDef, return TRUE; } -static inline void BattleAI_DoAIProcessing(struct AI_ThinkingStruct *aiThink, u32 battler, u32 battlerDef) +static inline void BattleAI_DoAIProcessing(struct AI_ThinkingStruct *aiThink, u32 battlerAtk, u32 battlerDef) { do { - if (gBattleMons[battler].pp[aiThink->movesetIndex] == 0) + if (gBattleMons[battlerAtk].pp[aiThink->movesetIndex] == 0) aiThink->moveConsidered = MOVE_NONE; else - aiThink->moveConsidered = gBattleMons[battler].moves[aiThink->movesetIndex]; + aiThink->moveConsidered = gBattleMons[battlerAtk].moves[aiThink->movesetIndex]; // 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(battler, battlerDef, aiThink->moveConsidered)) + && ShouldConsiderMoveForBattler(battlerAtk, battlerDef, aiThink->moveConsidered)) { if (aiThink->aiLogicId < ARRAY_COUNT(sBattleAiFuncTable) && sBattleAiFuncTable[aiThink->aiLogicId] != NULL) { // Call AI function aiThink->score[aiThink->movesetIndex] = - sBattleAiFuncTable[aiThink->aiLogicId](battler, + sBattleAiFuncTable[aiThink->aiLogicId](battlerAtk, battlerDef, aiThink->moveConsidered, aiThink->score[aiThink->movesetIndex]);