From 7a3cf5d3a3122b4219e39245a375b4a18fa90cfa Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Mon, 28 Apr 2025 02:34:09 -0400 Subject: [PATCH 1/2] Improve AI's defense against Focus Punch (#6713) --- include/config/ai.h | 3 +++ include/random.h | 1 + src/battle_ai_main.c | 7 +++++++ test/battle/move_effect/focus_punch.c | 14 ++++++++++++++ 4 files changed, 25 insertions(+) diff --git a/include/config/ai.h b/include/config/ai.h index b94b04b665..fdc7a789e0 100644 --- a/include/config/ai.h +++ b/include/config/ai.h @@ -50,6 +50,9 @@ // AI held item-based move scoring #define LOW_ACCURACY_THRESHOLD 75 // Moves with accuracy equal OR below this value are considered low accuracy +// AI move scoring +#define STATUS_MOVE_FOCUS_PUNCH_CHANCE 50 // Chance the AI will use a status move if the player's best move is Focus Punch + // AI damage calc considerations #define RISKY_AI_CRIT_STAGE_THRESHOLD 2 // Stat stages at which Risky will assume it gets a crit #define RISKY_AI_CRIT_THRESHOLD_GEN_1 128 // "Stat stage" at which Risky will assume it gets a crit with gen 1 mechanics (this translates to an X / 255 % crit threshold) diff --git a/include/random.h b/include/random.h index c8b4a039b5..b5ab0ac9bf 100644 --- a/include/random.h +++ b/include/random.h @@ -191,6 +191,7 @@ enum RandomTag RNG_RANDOM_TARGET, RNG_AI_PREDICT_ABILITY, RNG_AI_PREDICT_SWITCH, + 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 f3e7550123..e050bdd1bd 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -861,6 +861,13 @@ 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 + && GetMoveEffect(GetBestDmgMoveFromBattler(battlerDef, battlerAtk, AI_DEFENDING)) == EFFECT_FOCUS_PUNCH && RandomPercentage(RNG_AI_STATUS_FOCUS_PUNCH, STATUS_MOVE_FOCUS_PUNCH_CHANCE)) + { + RETURN_SCORE_MINUS(20); + } + // Don't use anything but super effective thawing moves if target is frozen if any other attack available if (((GetMoveType(move) == TYPE_FIRE && GetMovePower(move) != 0) || CanBurnHitThaw(move)) && effectiveness < UQ_4_12(2.0) && (gBattleMons[battlerDef].status1 & (STATUS1_FROSTBITE | STATUS1_FREEZE))) { diff --git a/test/battle/move_effect/focus_punch.c b/test/battle/move_effect/focus_punch.c index 55854ee80e..e6c10db6a7 100644 --- a/test/battle/move_effect/focus_punch.c +++ b/test/battle/move_effect/focus_punch.c @@ -99,3 +99,17 @@ AI_SINGLE_BATTLE_TEST("AI will Incapacitate -> Substitute -> Focus Punch if able TURN { MOVE(player, MOVE_DISCHARGE); EXPECT_MOVE(opponent, MOVE_FOCUS_PUNCH); } } } + +AI_SINGLE_BATTLE_TEST("AI won't use status moves if the player's best attacking move is Focus Punch") +{ + PASSES_RANDOMLY(STATUS_MOVE_FOCUS_PUNCH_CHANCE, 100, RNG_AI_STATUS_FOCUS_PUNCH); + GIVEN { + ASSUME(GetMoveEffect(MOVE_FOCUS_PUNCH) == EFFECT_FOCUS_PUNCH); + ASSUME(GetMoveCategory(MOVE_SWORDS_DANCE) == DAMAGE_CATEGORY_STATUS); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_SNORLAX) { Moves(MOVE_FOCUS_PUNCH, MOVE_TACKLE); } + OPPONENT(SPECIES_CLEFABLE) { Moves(MOVE_PLAY_ROUGH, MOVE_SWORDS_DANCE); } + } WHEN { + TURN { MOVE(player, MOVE_FOCUS_PUNCH); EXPECT_MOVE(opponent, MOVE_PLAY_ROUGH); } + } +} From 96a8035dec386e65c59b0619324ad55bedd6ffac Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Mon, 28 Apr 2025 02:48:21 -0400 Subject: [PATCH 2/2] Fix party data assignment (#6712) --- src/battle_ai_main.c | 4 ++-- src/battle_ai_switch_items.c | 5 ++--- test/battle/ai/ai_switching.c | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index e050bdd1bd..beec380c5a 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -304,8 +304,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); diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 79027dffd4..f144e5998d 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -181,11 +181,10 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) //Variable initialization u8 opposingPosition, atkType1, atkType2, defType1, defType2; s32 i, damageDealt = 0, maxDamageDealt = 0, damageTaken = 0, maxDamageTaken = 0; - u32 aiMove, playerMove, aiBestMove = MOVE_NONE, aiAbility = AI_DATA->abilities[battler], opposingBattler, weather = AI_GetWeather(); + u32 aiMove, playerMove, aiBestMove = MOVE_NONE, aiAbility = AI_DATA->abilities[battler], opposingBattler; bool32 getsOneShot = FALSE, hasStatusMove = FALSE, hasSuperEffectiveMove = FALSE; u16 typeEffectiveness = UQ_4_12(1.0); //baseline typing damage enum BattleMoveEffects aiMoveEffect; - uq4_12_t effectiveness; // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer if (!(AI_THINKING_STRUCT->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)) @@ -256,7 +255,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) playerMove = gBattleMons[opposingBattler].moves[i]; if (playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH) { - damageTaken = AI_CalcDamage(playerMove, opposingBattler, battler, &effectiveness, FALSE, weather).median; + damageTaken = AI_GetDamage(opposingBattler, battler, i, AI_DEFENDING, AI_DATA); if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move { return maxDamageTaken = damageTaken; diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 14ebb19dda..e8921cd3ac 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -556,7 +556,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if mon would ASSUME(GetMoveType(MOVE_THUNDERBOLT) == TYPE_ELECTRIC); ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND); - AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); PLAYER(SPECIES_ELECTRODE) { Moves(MOVE_THUNDERBOLT, MOVE_THUNDER_WAVE, MOVE_THUNDER_SHOCK); } OPPONENT(SPECIES_PELIPPER) { Moves(MOVE_EARTHQUAKE); }; OPPONENT(SPECIES_RHYDON) { Moves(MOVE_EARTHQUAKE); Ability(ABILITY_ROCK_HEAD); }