From 95a02dddb9887906ce973320ed7a0cd2757b4c2d Mon Sep 17 00:00:00 2001 From: surskitty Date: Wed, 9 Jul 2025 18:11:39 -0400 Subject: [PATCH] Improved logic for Guard Split and Power Split. (#7298) --- include/config/ai.h | 6 ++ src/battle_ai_main.c | 121 ++++++++++++++++++++++++++++-------- test/battle/ai/ai_doubles.c | 54 ++++++++++++++++ 3 files changed, 155 insertions(+), 26 deletions(-) diff --git a/include/config/ai.h b/include/config/ai.h index 91ee3525ae..c51ff6d790 100644 --- a/include/config/ai.h +++ b/include/config/ai.h @@ -79,4 +79,10 @@ #define FRIENDLY_FIRE_NORMAL_THRESHOLD 3 #define FRIENDLY_FIRE_CONSERVATIVE_THRESHOLD 4 +// AI's desired stat changes for Guard Split and Power Split, treated as % +#define GUARD_SPLIT_ALLY_PERCENTAGE 200 +#define GUARD_SPLIT_ENEMY_PERCENTAGE 50 +#define POWER_SPLIT_ALLY_PERCENTAGE 150 +#define POWER_SPLIT_ENEMY_PERCENTAGE 50 + #endif // GUARD_CONFIG_AI_H diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index f3976fae4a..dae2895d96 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -2455,11 +2455,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; case EFFECT_POWER_SPLIT: - if (IsTargetingPartner(battlerAtk, battlerDef)) - { - ADJUST_SCORE(-10); - } - else + if (!IsTargetingPartner(battlerAtk, battlerDef)) { u32 atkAttack = gBattleMons[battlerAtk].attack; u32 defAttack = gBattleMons[battlerDef].attack; @@ -2472,11 +2468,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; case EFFECT_GUARD_SPLIT: - if (IsTargetingPartner(battlerAtk, battlerDef)) - { - ADJUST_SCORE(-10); - } - else + if (!IsTargetingPartner(battlerAtk, battlerDef)) { u32 atkDefense = gBattleMons[battlerAtk].defense; u32 defDefense = gBattleMons[battlerDef].defense; @@ -3530,6 +3522,55 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) && gBattleMons[battlerAtkPartner].hp < gBattleMons[battlerAtkPartner].maxHP / 2) RETURN_SCORE_PLUS(WEAK_EFFECT); break; + case EFFECT_SPEED_SWAP: + break; + case EFFECT_GUARD_SPLIT: + { + u32 atkDefense = gBattleMons[battlerAtk].defense; + u32 defDefense = gBattleMons[battlerDef].defense; + u32 atkSpDefense = gBattleMons[battlerAtk].spDefense; + u32 defSpDefense = gBattleMons[battlerDef].spDefense; + + // It's actually * 100 and / 2 + u32 newDefense = (atkDefense + defDefense) * 50; + u32 newSpDefense = (atkSpDefense + defSpDefense) * 50; + + // We want to massively raise our defense. + if (newDefense > atkDefense * GUARD_SPLIT_ALLY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + if (newSpDefense > atkSpDefense * GUARD_SPLIT_ALLY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + if (newDefense > defDefense * GUARD_SPLIT_ALLY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + if (newSpDefense > defSpDefense * GUARD_SPLIT_ALLY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + + ADJUST_SCORE(WORST_EFFECT); + break; + } + case EFFECT_POWER_SPLIT: + { + u32 atkAttack = gBattleMons[battlerAtk].attack; + u32 defAttack = gBattleMons[battlerDef].attack; + u32 atkSpAttack = gBattleMons[battlerAtk].spAttack; + u32 defSpAttack = gBattleMons[battlerDef].spAttack; + + // * 100 and / 2 + u32 newAttack = (atkAttack + defAttack) * 50; + u32 newSpAtk = (atkSpAttack + defSpAttack) * 50; + + if (HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL) && newAttack > atkAttack * POWER_SPLIT_ALLY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL) && newAttack > defAttack * POWER_SPLIT_ALLY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + if (HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL) && newSpAtk > atkSpAttack * POWER_SPLIT_ALLY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL) && newSpAtk > defSpAttack * POWER_SPLIT_ALLY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + + ADJUST_SCORE(WORST_EFFECT); + break; + } default: break; } // attacker move effects @@ -4721,26 +4762,54 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) if (gBattleMons[battlerDef].speed > gBattleMons[battlerAtk].speed) ADJUST_SCORE(DECENT_EFFECT); break; - case EFFECT_GUARD_SPLIT: - { - u32 newDefense = (gBattleMons[battlerAtk].defense + gBattleMons[battlerDef].defense) / 2; - u32 newSpDef = (gBattleMons[battlerAtk].spDefense + gBattleMons[battlerDef].spDefense) / 2; +case EFFECT_GUARD_SPLIT: + { + u32 atkDefense = gBattleMons[battlerAtk].defense; + u32 defDefense = gBattleMons[battlerDef].defense; + u32 atkSpDefense = gBattleMons[battlerAtk].spDefense; + u32 defSpDefense = gBattleMons[battlerDef].spDefense; - if ((newDefense > gBattleMons[battlerAtk].defense && newSpDef >= gBattleMons[battlerAtk].spDefense) - || (newSpDef > gBattleMons[battlerAtk].spDefense && newDefense >= gBattleMons[battlerAtk].defense)) - ADJUST_SCORE(DECENT_EFFECT); - } + // It's actually * 100 and / 2; + u32 newDefense = (atkDefense + defDefense) * 50; + u32 newSpDefense = (atkSpDefense + defSpDefense) * 50; + + // We want to massively raise our defense. + if (newDefense > atkDefense * GUARD_SPLIT_ALLY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + if (newSpDefense > atkSpDefense * GUARD_SPLIT_ALLY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + + // We also want to massively lower theirs. + if (newDefense < defDefense * GUARD_SPLIT_ENEMY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + if (newSpDefense < defSpDefense * GUARD_SPLIT_ENEMY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + + ADJUST_SCORE(WORST_EFFECT); break; + } case EFFECT_POWER_SPLIT: - { - u32 newAttack = (gBattleMons[battlerAtk].attack + gBattleMons[battlerDef].attack) / 2; - u32 newSpAtk = (gBattleMons[battlerAtk].spAttack + gBattleMons[battlerDef].spAttack) / 2; + { + u32 atkAttack = gBattleMons[battlerAtk].attack; + u32 defAttack = gBattleMons[battlerDef].attack; + u32 atkSpAttack = gBattleMons[battlerAtk].spAttack; + u32 defSpAttack = gBattleMons[battlerDef].spAttack; - if ((newAttack > gBattleMons[battlerAtk].attack && newSpAtk >= gBattleMons[battlerAtk].spAttack) - || (newSpAtk > gBattleMons[battlerAtk].spAttack && newAttack >= gBattleMons[battlerAtk].attack)) - ADJUST_SCORE(DECENT_EFFECT); - } - break; + // It's actually * 100 and / 2 + u32 newAttack = (atkAttack + defAttack) * 50; + u32 newSpAtk = (atkSpAttack + defSpAttack) * 50; + + if (HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL) && newAttack > atkAttack * POWER_SPLIT_ALLY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + if (HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL) && newSpAtk > atkSpAttack * POWER_SPLIT_ALLY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + if (newAttack < defAttack * POWER_SPLIT_ENEMY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + if (newSpAtk < defSpAttack * POWER_SPLIT_ENEMY_PERCENTAGE) + ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); + + ADJUST_SCORE(WORST_EFFECT); + } case EFFECT_ELECTRIC_TERRAIN: case EFFECT_MISTY_TERRAIN: if (gStatuses3[battlerAtk] & STATUS3_YAWN && IsBattlerGrounded(battlerAtk)) diff --git a/test/battle/ai/ai_doubles.c b/test/battle/ai/ai_doubles.c index 5edc271fff..243c6611e9 100644 --- a/test/battle/ai/ai_doubles.c +++ b/test/battle/ai/ai_doubles.c @@ -410,3 +410,57 @@ AI_DOUBLE_BATTLE_TEST("AI prioritizes Skill Swapping Contrary to allied mons tha } } +AI_DOUBLE_BATTLE_TEST("AI uses Guard Split to improve its stats") +{ + + u32 player, opponent; + + PARAMETRIZE { player = SPECIES_SHUCKLE; opponent = SPECIES_PHEROMOSA; } + PARAMETRIZE { player = SPECIES_PHEROMOSA; opponent = SPECIES_SHUCKLE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_GUARD_SPLIT) == EFFECT_GUARD_SPLIT); + ASSUME(gSpeciesInfo[SPECIES_PHEROMOSA].baseDefense < gSpeciesInfo[SPECIES_WOBBUFFET].baseDefense); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].baseDefense < gSpeciesInfo[SPECIES_SHUCKLE].baseDefense); + ASSUME(gSpeciesInfo[SPECIES_PHEROMOSA].baseSpDefense < gSpeciesInfo[SPECIES_WOBBUFFET].baseSpDefense); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].baseSpDefense < gSpeciesInfo[SPECIES_SHUCKLE].baseSpDefense); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE); + PLAYER(player); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_GUARD_SPLIT, MOVE_NIGHT_SHADE); } + OPPONENT(opponent); + } WHEN { + if (player == SPECIES_SHUCKLE) + TURN { EXPECT_MOVE(opponentLeft, MOVE_GUARD_SPLIT, target:playerLeft); } + else + TURN { EXPECT_MOVE(opponentLeft, MOVE_GUARD_SPLIT, target:opponentRight); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI uses Power Split to improve its stats") +{ + + u32 player, opponent; + + PARAMETRIZE { player = SPECIES_SHUCKLE; opponent = SPECIES_PHEROMOSA; } + PARAMETRIZE { player = SPECIES_PHEROMOSA; opponent = SPECIES_SHUCKLE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_POWER_SPLIT) == EFFECT_POWER_SPLIT); + ASSUME(gSpeciesInfo[SPECIES_PHEROMOSA].baseAttack > gSpeciesInfo[SPECIES_WOBBUFFET].baseAttack); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].baseAttack > gSpeciesInfo[SPECIES_SHUCKLE].baseAttack); + ASSUME(gSpeciesInfo[SPECIES_PHEROMOSA].baseSpAttack > gSpeciesInfo[SPECIES_WOBBUFFET].baseSpAttack); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].baseSpAttack > gSpeciesInfo[SPECIES_SHUCKLE].baseSpAttack); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE); + PLAYER(player); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_POWER_SPLIT, MOVE_TACKLE, MOVE_ROUND); } + OPPONENT(opponent) { Moves(MOVE_TACKLE, MOVE_ROUND); } + } WHEN { + if (player == SPECIES_PHEROMOSA) + TURN { EXPECT_MOVE(opponentLeft, MOVE_POWER_SPLIT, target:playerLeft); } + else + TURN { EXPECT_MOVE(opponentLeft, MOVE_POWER_SPLIT, target:opponentRight); } + } +} +