AI_FLAG_ATTACKS_PARTNER with a config for bloodthirstiness (#7401)

This commit is contained in:
surskitty 2025-08-06 10:22:34 -04:00 committed by GitHub
parent acc82e7d79
commit e8b6d40f18
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 312 additions and 124 deletions

View File

@ -54,6 +54,9 @@ This flag is divided into two components to calculate the best available move fo
This is different to `AI_FLAG_CHECK_BAD_MOVE` as it calculates how poor a move is and not whether it will fail or not.
## `AI_FLAG_ATTACKS_PARTNER`
This flag is meant for double battles where both of the opponents hate each other. They prioritize damage to their 'partner' over the player.
## `AI_FLAG_FORCE_SETUP_FIRST_TURN`
AI will prioritize using setup moves on the first turn at the expense of all else. These include stat buffs, field effects, status moves, etc. AI_FLAG_CHECK_VIABILITY will instead do this when the AI determines it makes sense.

View File

@ -241,8 +241,16 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData
// partner logic
bool32 IsTargetingPartner(u32 battlerAtk, u32 battlerDef);
// IsTargetingPartner includes a check to make sure the adjacent pokemon is truly a partner.
u32 GetAllyChosenMove(u32 battlerId);
bool32 IsValidDoubleBattle(u32 battlerAtk);
bool32 IsBattle1v1();
// IsBattle1v1 is distinct from !IsDoubleBattle. If the player is fighting Maxie and Tabitha, with Steven as their partner, and both Tabitha and Steven have run out of Pokemon, the battle is 1v1, even though mechanically it is a Double Battle for how battlers and flags are set.
// Most AI checks should be using IsBattle1v1; most engine checks should be using !IsDoubleBattle
bool32 HasTwoOpponents(u32 battler);
// HasTwoOpponents checks if the opposing side has two pokemon. Partner state is irrelevant. e.g., Dragon Darts hits one time with two opponents and twice with one opponent.
bool32 HasPartner(u32 battler);
bool32 HasPartnerIgnoreFlags(u32 battler);
// HasPartner respects the Attacks Partner AI flag; HasPartnerIgnoreFlags checks only if a live pokemon is adjacent.
bool32 DoesPartnerHaveSameMoveEffect(u32 battlerAtkPartner, u32 battlerDef, u32 move, u32 partnerMove);
bool32 PartnerHasSameMoveEffectWithoutTarget(u32 battlerAtkPartner, u32 move, u32 partnerMove);
bool32 PartnerMoveEffectIsStatusSameTarget(u32 battlerAtkPartner, u32 battlerDef, u32 partnerMove);
@ -290,5 +298,6 @@ u32 GetIncomingMove(u32 battler, u32 opposingBattler, struct AiLogicData *aiData
bool32 HasLowAccuracyMove(u32 battlerAtk, u32 battlerDef);
bool32 HasBattlerSideAbility(u32 battlerDef, u32 ability, struct AiLogicData *aiData);
u32 GetThinkingBattler(u32 battler);
bool32 IsNaturalEnemy(u32 speciesAttacker, u32 speciesTarget);
#endif //GUARD_BATTLE_AI_UTIL_H

View File

@ -101,6 +101,9 @@
#define DOUBLE_TRICK_ROOM_ON_LAST_TURN_CHANCE 35 // both pokemon use Trick Room on turn Trick Room expires in the hopes both opponents used Protect to stall, getting a free refresh on the timer
#define TAILWIND_IN_TRICK_ROOM_CHANCE 35 // use Tailwind on turn Trick Room expires in the hopes both opponents used Protect to stall
#define AI_FLAG_ATTACKS_PARTNER_FOCUSES_PARTNER FALSE // if TRUE, AI_FLAG_ATTACKS_PARTNER prefers attacking the partner over the ally.
// This is treated as true regardless during wild battles with AI.
// 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

View File

@ -34,9 +34,10 @@
#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
#define AI_FLAG_SMART_TERA (1 << 27) // AI will make smarter decisions when choosing whether to terrastalize (default is to always tera whenever available).
#define AI_FLAG_ASSUME_STAB (1 << 28) // AI knows player's STAB moves, but nothing else. Restricted version of AI_FLAG_OMNISCIENT.
#define AI_FLAG_ASSUME_STATUS_MOVES (1 << 29) // AI has a chance to know certain non-damaging moves, and also Fake Out and Super Fang. Restricted version of AI_FLAG_OMNISCIENT.
#define AI_FLAG_ASSUME_STATUS_MOVES (1 << 29) // AI has a chance to know certain non-damaging moves, and also Fake Out and Super Fang. Restricted version of AI_FLAG_OMNISCIENT.
#define AI_FLAG_ATTACKS_PARTNER (1 << 30) // AI specific to double battles; AI can deliberately attack its 'partner.'
#define AI_FLAG_COUNT 30
#define AI_FLAG_COUNT 31
// Flags at and after 32 need different formatting, as in
// #define AI_FLAG_PLACEHOLDER ((u64)1 << 32)

View File

@ -57,7 +57,7 @@ bool32 WeatherChecker(u32 battler, u32 weather, enum FieldEffectOutcome desiredR
u32 i;
u32 battlersOnSide = 1;
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battler)))
if (HasPartner(battler))
battlersOnSide = 2;
for (i = 0; i < battlersOnSide; i++)
@ -92,7 +92,7 @@ bool32 FieldStatusChecker(u32 battler, u32 fieldStatus, enum FieldEffectOutcome
u32 battlersOnSide = 1;
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battler)))
if (HasPartner(battler))
battlersOnSide = 2;
for (i = 0; i < battlersOnSide; i++)
@ -230,7 +230,7 @@ static enum FieldEffectOutcome BenefitsFromSun(u32 battler)
if (DoesAbilityBenefitFromWeather(ability, B_WEATHER_SUN)
|| HasLightSensitiveMove(battler)
|| HasDamagingMoveOfType(battler, TYPE_FIRE)
|| HasBattlerSideMoveWithEffect(battler, EFFECT_HYDRO_STEAM))
|| HasMoveWithEffect(battler, EFFECT_HYDRO_STEAM))
return FIELD_EFFECT_POSITIVE;
if (HasMoveWithFlag(battler, MoveHas50AccuracyInSun) || HasDamagingMoveOfType(battler, TYPE_WATER) || gAiLogicData->abilities[battler] == ABILITY_DRY_SKIN)
@ -243,8 +243,7 @@ static enum FieldEffectOutcome BenefitsFromSun(u32 battler)
static enum FieldEffectOutcome BenefitsFromSandstorm(u32 battler)
{
if (DoesAbilityBenefitFromWeather(gAiLogicData->abilities[battler], B_WEATHER_SANDSTORM)
|| IS_BATTLER_OF_TYPE(battler, TYPE_ROCK)
|| HasBattlerSideMoveWithEffect(battler, EFFECT_SHORE_UP))
|| IS_BATTLER_OF_TYPE(battler, TYPE_ROCK))
return FIELD_EFFECT_POSITIVE;
if (gAiLogicData->holdEffects[battler] == HOLD_EFFECT_SAFETY_GOGGLES || IS_BATTLER_ANY_TYPE(battler, TYPE_ROCK, TYPE_GROUND, TYPE_STEEL))
@ -314,7 +313,7 @@ static enum FieldEffectOutcome BenefitsFromElectricTerrain(u32 battler)
return FIELD_EFFECT_POSITIVE;
bool32 grounded = IsBattlerGrounded(battler);
if (grounded && HasBattlerSideUsedMoveWithAdditionalEffect(FOE(battler), MOVE_EFFECT_SLEEP))
if (grounded && HasBattlerSideMoveWithAdditionalEffect(FOE(battler), MOVE_EFFECT_SLEEP))
return FIELD_EFFECT_POSITIVE;
if (grounded && ((gBattleMons[battler].status1 & STATUS1_SLEEP)
@ -322,6 +321,10 @@ static enum FieldEffectOutcome BenefitsFromElectricTerrain(u32 battler)
|| HasDamagingMoveOfType(battler, TYPE_ELECTRIC)))
return FIELD_EFFECT_POSITIVE;
if (HasBattlerSideMoveWithEffect(FOE(battler), EFFECT_RISING_VOLTAGE))
return FIELD_EFFECT_NEGATIVE;
return FIELD_EFFECT_NEUTRAL;
}
@ -331,21 +334,25 @@ static enum FieldEffectOutcome BenefitsFromGrassyTerrain(u32 battler)
if (DoesAbilityBenefitFromFieldStatus(gAiLogicData->abilities[battler], STATUS_FIELD_GRASSY_TERRAIN))
return FIELD_EFFECT_POSITIVE;
if (HasBattlerSideMoveWithEffect(battler, EFFECT_GRASSY_GLIDE))
if (HasMoveWithEffect(battler, EFFECT_GRASSY_GLIDE))
return FIELD_EFFECT_POSITIVE;
if (HasBattlerSideUsedMoveWithAdditionalEffect(battler, MOVE_EFFECT_FLORAL_HEALING))
if (HasMoveWithAdditionalEffect(battler, MOVE_EFFECT_FLORAL_HEALING))
return FIELD_EFFECT_POSITIVE;
bool32 grounded = IsBattlerGrounded(battler);
// Weaken spamming Earthquake, Magnitude, and Bulldoze.
if (grounded && (HasBattlerSideUsedMoveWithEffect(FOE(battler), EFFECT_EARTHQUAKE)
|| HasBattlerSideUsedMoveWithEffect(FOE(battler), EFFECT_MAGNITUDE)))
if (grounded && (HasBattlerSideMoveWithEffect(FOE(battler), EFFECT_EARTHQUAKE)
|| HasBattlerSideMoveWithEffect(FOE(battler), EFFECT_MAGNITUDE)))
return FIELD_EFFECT_POSITIVE;
if (grounded && HasDamagingMoveOfType(battler, TYPE_GRASS))
return FIELD_EFFECT_POSITIVE;
if (HasBattlerSideMoveWithEffect(FOE(battler), EFFECT_GRASSY_GLIDE))
return FIELD_EFFECT_NEGATIVE;
return FIELD_EFFECT_NEUTRAL;
}
@ -360,7 +367,7 @@ static enum FieldEffectOutcome BenefitsFromMistyTerrain(u32 battler)
bool32 grounded = IsBattlerGrounded(battler);
bool32 allyGrounded = FALSE;
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battler)))
if (HasPartner(battler))
allyGrounded = IsBattlerGrounded(BATTLE_PARTNER(battler));
if (HasMoveWithEffect(FOE(battler), EFFECT_REST) && IsBattlerGrounded(FOE(battler)))
@ -387,12 +394,12 @@ static enum FieldEffectOutcome BenefitsFromPsychicTerrain(u32 battler)
if (DoesAbilityBenefitFromFieldStatus(gAiLogicData->abilities[battler], STATUS_FIELD_PSYCHIC_TERRAIN))
return FIELD_EFFECT_POSITIVE;
if (HasBattlerSideMoveWithEffect(battler, EFFECT_EXPANDING_FORCE))
if (HasMoveWithEffect(battler, EFFECT_EXPANDING_FORCE))
return FIELD_EFFECT_POSITIVE;
bool32 grounded = IsBattlerGrounded(battler);
bool32 allyGrounded = FALSE;
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battler)))
if (HasPartner(battler))
allyGrounded = IsBattlerGrounded(BATTLE_PARTNER(battler));
// don't bother if we're not grounded
@ -408,6 +415,9 @@ static enum FieldEffectOutcome BenefitsFromPsychicTerrain(u32 battler)
if (grounded && (HasDamagingMoveOfType(battler, TYPE_PSYCHIC)))
return FIELD_EFFECT_POSITIVE;
if (HasBattlerSideMoveWithEffect(FOE(battler), EFFECT_EXPANDING_FORCE))
return FIELD_EFFECT_NEGATIVE;
if (HasBattlerSideAbility(battler, ABILITY_GALE_WINGS, gAiLogicData)
|| HasBattlerSideAbility(battler, ABILITY_TRIAGE, gAiLogicData)
|| HasBattlerSideAbility(battler, ABILITY_PRANKSTER, gAiLogicData))
@ -429,7 +439,7 @@ static enum FieldEffectOutcome BenefitsFromTrickRoom(u32 battler)
else
return FIELD_EFFECT_NEGATIVE;
}
// First checking if we have enough priority for one pokemon to disregard Trick Room entirely.
if (!(gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN))
{

View File

@ -51,6 +51,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
static s32 AI_ForceSetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32 score);
static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, u32 move, s32 score);
static s32 AI_TryTo2HKO(u32 battlerAtk, u32 battlerDef, u32 move, s32 score);
static s32 AI_AttacksPartner(u32 battlerAtk, u32 battlerDef, u32 move, s32 score);
static s32 AI_PreferBatonPass(u32 battlerAtk, u32 battlerDef, u32 move, s32 score);
static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score);
static s32 AI_Roaming(u32 battlerAtk, u32 battlerDef, u32 move, s32 score);
@ -91,10 +92,10 @@ static s32 (*const sBattleAiFuncTable[])(u32, u32, u32, s32) =
[24] = NULL, // AI_FLAG_PREDICT_INCOMING_MON
[25] = AI_CheckPpStall, // AI_FLAG_PP_STALL_PREVENTION
[26] = NULL, // AI_FLAG_PREDICT_MOVE
[27] = NULL, // Unused
[28] = NULL, // Unused
[29] = NULL, // Unused
[30] = NULL, // Unused
[27] = NULL, // AI_FLAG_SMART_TERA
[28] = NULL, // AI_FLAG_ASSUME_STAB
[29] = NULL, // AI_FLAG_ASSUME_STATUS_MOVES
[30] = AI_AttacksPartner, // AI_FLAG_ATTACKS_PARTNER
[31] = NULL, // Unused
[32] = NULL, // Unused
[33] = NULL, // Unused
@ -247,6 +248,18 @@ void BattleAI_SetupFlags(void)
// smart wild AI
gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] = GetAiFlags(0xFFFF);
gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_RIGHT] = GetAiFlags(0xFFFF);
// The check is here because wild natural enemies are not symmetrical.
if (B_WILD_NATURAL_ENEMIES && IsDoubleBattle())
{
u32 speciesLeft = GetMonData(&gEnemyParty[0], MON_DATA_SPECIES);
u32 speciesRight = GetMonData(&gEnemyParty[1], MON_DATA_SPECIES);
if (IsNaturalEnemy(speciesLeft, speciesRight))
gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] |= AI_FLAG_ATTACKS_PARTNER;
if (IsNaturalEnemy(speciesRight, speciesLeft))
gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_RIGHT] |= AI_FLAG_ATTACKS_PARTNER;
}
}
else
{
@ -1059,6 +1072,9 @@ void BattleAI_DoAIProcessing_PredictedSwitchin(struct AiThinkingStruct *aiThink,
// AI_FLAG_CHECK_BAD_MOVE - decreases move scores
static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
{
if (IsTargetingPartner(battlerAtk, battlerDef))
return score;
// move data
enum BattleMoveEffects moveEffect = GetMoveEffect(move);
u32 nonVolatileStatus = GetMoveNonVolatileStatus(move);
@ -1066,7 +1082,9 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
u32 moveTarget = GetBattlerMoveTargetType(battlerAtk, move);
struct AiLogicData *aiData = gAiLogicData;
uq4_12_t effectiveness = aiData->effectiveness[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex];
bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk);
bool32 isBattle1v1 = IsBattle1v1();
bool32 hasTwoOpponents = HasTwoOpponents(battlerAtk);
bool32 hasPartner = HasPartner(battlerAtk);
u32 i;
u32 weather;
u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData);
@ -1074,9 +1092,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
u32 abilityDef = aiData->abilities[battlerDef];
s32 atkPriority = GetBattleMovePriority(battlerAtk, abilityAtk, move);
if (IsTargetingPartner(battlerAtk, battlerDef))
return score;
SetTypeBeforeUsingMove(move, battlerAtk);
moveType = GetBattleMoveType(move);
@ -1208,7 +1223,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
} // def ability checks
// target partner ability checks & not attacking partner
if (isDoubleBattle)
if (hasTwoOpponents)
{
switch (aiData->abilities[BATTLE_PARTNER(battlerDef)])
{
@ -1479,7 +1494,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10);
break;
case EFFECT_ROTOTILLER:
if (isDoubleBattle)
if (hasPartner)
{
if (!(IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GRASS)
&& IsBattlerGrounded(battlerAtk)
@ -1510,12 +1525,12 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-8);
break;
}
else if (!isDoubleBattle)
else if (!hasPartner)
{
ADJUST_SCORE(-10); // no partner and our stats wont rise, so don't use
}
if (isDoubleBattle)
if (hasPartner)
{
if (aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_PLUS || aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_MINUS)
{
@ -1541,12 +1556,12 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF))
ADJUST_SCORE(-8);
}
else if (!isDoubleBattle)
else if (!hasPartner)
{
ADJUST_SCORE(-10); // our stats wont rise from this move
}
if (isDoubleBattle)
if (hasPartner)
{
if (aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_PLUS || aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_MINUS)
{
@ -1875,7 +1890,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-9);
break;
case EFFECT_PERISH_SONG:
if (isDoubleBattle)
if (!isBattle1v1)
{
if (CountUsablePartyMons(battlerAtk) == 0
&& aiData->abilities[battlerAtk] != ABILITY_SOUNDPROOF
@ -1908,23 +1923,23 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break;
case EFFECT_SANDSTORM:
if (weather & (B_WEATHER_SANDSTORM | B_WEATHER_PRIMAL_ANY)
|| IsMoveEffectWeather(aiData->partnerMove))
|| (HasPartner(battlerAtk) && IsMoveEffectWeather(aiData->partnerMove)))
ADJUST_SCORE(-8);
break;
case EFFECT_SUNNY_DAY:
if (weather & (B_WEATHER_SUN | B_WEATHER_PRIMAL_ANY)
|| IsMoveEffectWeather(aiData->partnerMove))
|| (HasPartner(battlerAtk) && IsMoveEffectWeather(aiData->partnerMove)))
ADJUST_SCORE(-8);
break;
case EFFECT_RAIN_DANCE:
if (weather & (B_WEATHER_RAIN | B_WEATHER_PRIMAL_ANY)
|| IsMoveEffectWeather(aiData->partnerMove))
|| (HasPartner(battlerAtk) && IsMoveEffectWeather(aiData->partnerMove)))
ADJUST_SCORE(-8);
break;
case EFFECT_HAIL:
case EFFECT_SNOWSCAPE:
if (weather & (B_WEATHER_ICY_ANY | B_WEATHER_PRIMAL_ANY)
|| IsMoveEffectWeather(aiData->partnerMove))
|| (HasPartner(battlerAtk) && IsMoveEffectWeather(aiData->partnerMove)))
ADJUST_SCORE(-8);
break;
case EFFECT_ATTRACT:
@ -2022,8 +2037,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break;
case EFFECT_FOLLOW_ME:
case EFFECT_HELPING_HAND:
if (!isDoubleBattle
|| !IsBattlerAlive(BATTLE_PARTNER(battlerAtk))
if (!hasPartner
|| PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)
|| (aiData->partnerMove != MOVE_NONE && IsBattleMoveStatus(aiData->partnerMove))
|| gBattleStruct->monToSwitchIntoId[BATTLE_PARTNER(battlerAtk)] != PARTY_SIZE) //Partner is switching out.
@ -2102,11 +2116,11 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break;
case EFFECT_FLOWER_SHIELD:
if (!IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GRASS)
&& !(isDoubleBattle && IS_BATTLER_OF_TYPE(BATTLE_PARTNER(battlerAtk), TYPE_GRASS)))
&& !(hasPartner && IS_BATTLER_OF_TYPE(BATTLE_PARTNER(battlerAtk), TYPE_GRASS)))
ADJUST_SCORE(-10);
break;
case EFFECT_AROMATIC_MIST:
if (!isDoubleBattle || gBattleMons[BATTLE_PARTNER(battlerAtk)].hp == 0 || !BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_SPDEF))
if (!hasPartner || !BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_SPDEF))
ADJUST_SCORE(-10);
break;
case EFFECT_BIDE:
@ -2243,7 +2257,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
decreased = TRUE;
}
case PROTECT_CRAFTY_SHIELD:
if (!isDoubleBattle)
if (!hasPartner)
{
ADJUST_SCORE(-10);
decreased = TRUE;
@ -2279,7 +2293,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
}
else if (gDisableStructs[battlerAtk].protectUses == 1 && Random() % 100 < 50)
{
if (!isDoubleBattle)
if (isBattle1v1)
ADJUST_SCORE(-6);
else
ADJUST_SCORE(-10); //Don't try double protecting in doubles
@ -2327,7 +2341,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break;
}
if (isDoubleBattle)
if (hasPartner)
{
if (IsHazardMove(aiData->partnerMove) // partner is going to set up hazards
&& AI_IsFaster(BATTLE_PARTNER(battlerAtk), battlerAtk, aiData->partnerMove, predictedMove, CONSIDER_PRIORITY)) // partner is going to set up before the potential Defog
@ -2540,7 +2554,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10);
break;
case EFFECT_PLEDGE:
if (isDoubleBattle && gBattleMons[BATTLE_PARTNER(battlerAtk)].hp > 0)
if (hasPartner && gBattleMons[BATTLE_PARTNER(battlerAtk)].hp > 0)
{
if (aiData->partnerMove != MOVE_NONE
&& GetMoveEffect(aiData->partnerMove) == EFFECT_PLEDGE
@ -2723,7 +2737,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
}
else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
ADJUST_SCORE(-10);
else if (isDoubleBattle)
else if (hasPartner)
{
if (!IsTargetingPartner(battlerAtk, battlerDef))
ADJUST_SCORE(-10);
@ -2742,14 +2756,14 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
}
break;
case EFFECT_QUASH:
if (!isDoubleBattle
if (!hasPartner
|| AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
ADJUST_SCORE(-10);
break;
case EFFECT_AFTER_YOU:
if (!IsTargetingPartner(battlerAtk, battlerDef)
|| !isDoubleBattle
|| !hasPartner
|| AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
ADJUST_SCORE(-10);
@ -2976,15 +2990,16 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
SetTypeBeforeUsingMove(move, battlerAtk);
moveType = GetBattleMoveType(move);
bool32 hasPartner = HasPartner(battlerAtk);
u32 friendlyFireThreshold = GetFriendlyFireKOThreshold(battlerAtk);
u32 noOfHitsToKOPartner = GetNoOfHitsToKOBattler(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING);
bool32 wouldPartnerFaint = CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING)
&& !partnerProtecting && IsBattlerAlive(battlerAtkPartner);
bool32 wouldPartnerFaint = hasPartner && CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING)
&& !partnerProtecting;
bool32 isFriendlyFireOK = !wouldPartnerFaint && (noOfHitsToKOPartner == 0 || noOfHitsToKOPartner > friendlyFireThreshold);
// check what effect partner is using
if (aiData->partnerMove != 0)
if (aiData->partnerMove != 0 && hasPartner)
{
switch (partnerEffect)
{
@ -3040,7 +3055,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
switch (effect)
{
case EFFECT_HELPING_HAND:
if (!IsBattlerAlive(battlerAtkPartner) || !HasDamagingMove(battlerAtkPartner))
if (!hasPartner || !HasDamagingMove(battlerAtkPartner))
ADJUST_SCORE(-20);
break;
case EFFECT_PERISH_SONG:
@ -3076,7 +3091,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
// Both Pokemon use Trick Room on the final turn of Trick Room to anticipate both opponents Protecting to stall out.
// This unsets Trick Room and resets it with a full timer.
case EFFECT_TRICK_ROOM:
if (gFieldStatuses & STATUS_FIELD_TRICK_ROOM && gFieldTimers.trickRoomTimer == gBattleTurnCounter
if (hasPartner && gFieldStatuses & STATUS_FIELD_TRICK_ROOM && gFieldTimers.trickRoomTimer == gBattleTurnCounter
&& ShouldSetFieldStatus(battlerAtk, STATUS_FIELD_TRICK_ROOM)
&& HasMoveWithEffect(battlerAtkPartner, MOVE_TRICK_ROOM)
&& RandomPercentage(RNG_AI_REFRESH_TRICK_ROOM_ON_LAST_TURN, DOUBLE_TRICK_ROOM_ON_LAST_TURN_CHANCE))
@ -3702,9 +3717,8 @@ static inline bool32 ShouldUseSpreadDamageMove(u32 battlerAtk, u32 move, u32 mov
u32 partnerBattler = BATTLE_PARTNER(battlerAtk);
u32 noOfHitsToFaintPartner = GetNoOfHitsToKOBattler(battlerAtk, partnerBattler, moveIndex, AI_ATTACKING);
u32 friendlyFireThreshold = GetFriendlyFireKOThreshold(battlerAtk);
return (IsDoubleBattle()
return (HasPartnerIgnoreFlags(battlerAtk)
&& noOfHitsToFaintPartner != 0 // Immunity check
&& IsBattlerAlive(partnerBattler)
&& GetBattlerMoveTargetType(battlerAtk, move) == MOVE_TARGET_FOES_AND_ALLY
&& !(noOfHitsToFaintPartner < friendlyFireThreshold && hitsToFaintOpposingBattler == 1)
&& noOfHitsToFaintPartner < (friendlyFireThreshold * 2));
@ -3906,7 +3920,10 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData);
u32 predictedType = GetMoveType(predictedMove);
u32 predictedMoveSlot = GetMoveSlot(GetMovesArray(battlerDef), predictedMove);
bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk);
bool32 isBattle1v1 = IsBattle1v1();
bool32 hasTwoOpponents = HasTwoOpponents(battlerAtk);
bool32 hasPartner = HasPartner(battlerAtk);
bool32 moveTargetsBothOpponents = hasTwoOpponents && (gMovesInfo[move].target & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_ALL_BATTLERS));
u32 i;
// The AI should understand that while Dynamaxed, status moves function like Protect.
@ -4083,7 +4100,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
if (AnyStatIsRaised(BATTLE_PARTNER(battlerAtk))
|| PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))
break;
score += AI_TryToClearStats(battlerAtk, battlerDef, isDoubleBattle);
score += AI_TryToClearStats(battlerAtk, battlerDef, moveTargetsBothOpponents);
break;
case EFFECT_ROAR:
if ((IsSoundMove(move) && aiData->abilities[battlerDef] == ABILITY_SOUNDPROOF)
@ -4091,7 +4108,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
break;
else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
break;
score += AI_TryToClearStats(battlerAtk, battlerDef, isDoubleBattle);
score += AI_TryToClearStats(battlerAtk, battlerDef, moveTargetsBothOpponents);
break;
case EFFECT_MULTI_HIT:
case EFFECT_TRIPLE_KICK:
@ -4228,7 +4245,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
case EFFECT_HIT_ESCAPE:
case EFFECT_PARTING_SHOT:
case EFFECT_CHILLY_RECEPTION:
if (!IsDoubleBattle())
if (!hasPartner)
{
switch (ShouldPivot(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, movesetIndex))
{
@ -4346,7 +4363,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
{
ADJUST_SCORE(ProtectChecks(battlerAtk, battlerDef, move, predictedMove));
}
else if (isDoubleBattle && GetBattlerMoveTargetType(BATTLE_PARTNER(battlerAtk), aiData->partnerMove) & MOVE_TARGET_FOES_AND_ALLY)
else if (hasPartner && GetBattlerMoveTargetType(BATTLE_PARTNER(battlerAtk), aiData->partnerMove) & MOVE_TARGET_FOES_AND_ALLY)
{
if (aiData->abilities[battlerAtk] != ABILITY_TELEPATHY)
ADJUST_SCORE(ProtectChecks(battlerAtk, battlerDef, move, predictedMove));
@ -4516,7 +4533,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
score += AI_ShouldCopyStatChanges(battlerAtk, battlerDef);
break;
case EFFECT_SEMI_INVULNERABLE:
if (predictedMove != MOVE_NONE && !isDoubleBattle)
if (predictedMove != MOVE_NONE && isBattle1v1)
{
enum BattleMoveEffects predictedEffect = GetMoveEffect(predictedMove);
if ((AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
@ -4565,11 +4582,11 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
IncreaseConfusionScore(battlerAtk, battlerDef, move, &score);
break;
case EFFECT_FURY_CUTTER:
if (!isDoubleBattle && aiData->holdEffects[battlerAtk] == HOLD_EFFECT_METRONOME)
if (isBattle1v1 && aiData->holdEffects[battlerAtk] == HOLD_EFFECT_METRONOME)
ADJUST_SCORE(GOOD_EFFECT);
break;
case EFFECT_ATTRACT:
if (!isDoubleBattle
if (isBattle1v1
&& (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))
&& BattlerWillFaintFromSecondaryDamage(battlerDef, aiData->abilities[battlerDef]))
break; // Don't use if the attract won't have a change to activate
@ -4594,7 +4611,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
}
else if (!AreAnyHazardsOnSide(GetBattlerSide(battlerDef)) || CountUsablePartyMons(battlerDef) == 0) //Don't blow away hazards if you set them up
{
if (isDoubleBattle)
if (hasPartner)
{
if (IsHazardMove(aiData->partnerMove) // Partner is going to set up hazards
&& AI_IsSlower(battlerAtk, BATTLE_PARTNER(battlerAtk), move, predictedMove, CONSIDER_PRIORITY)) // Partner going first
@ -4606,11 +4623,11 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
case EFFECT_TORMENT:
break;
case EFFECT_FOLLOW_ME:
if (isDoubleBattle
if (hasPartner
&& GetMoveTarget(move) == MOVE_TARGET_USER
&& !IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef])
&& (!IsPowderMove(move) || IsAffectedByPowder(battlerDef, aiData->abilities[battlerDef], aiData->holdEffects[battlerDef])) // Rage Powder doesn't affect powder immunities
&& IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))
&& (!IsPowderMove(move) || IsAffectedByPowder(battlerDef, aiData->abilities[battlerDef], aiData->holdEffects[battlerDef])))
// Rage Powder doesn't affect powder immunities
{
u32 predictedMoveOnPartner = gLastMoves[BATTLE_PARTNER(battlerAtk)];
if (predictedMoveOnPartner != MOVE_NONE && !IsBattleMoveStatus(predictedMoveOnPartner))
@ -4684,7 +4701,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
case HOLD_EFFECT_EJECT_BUTTON:
//if (!IsRaidBattle() && GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX && gNewBS->dynamaxData.timer[battlerDef] > 1 &&
if (HasDamagingMove(battlerAtk)
|| (isDoubleBattle && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && HasDamagingMove(BATTLE_PARTNER(battlerAtk))))
|| (hasPartner && HasDamagingMove(BATTLE_PARTNER(battlerAtk))))
ADJUST_SCORE(DECENT_EFFECT); // Force 'em out next turn
break;
default:
@ -5013,7 +5030,7 @@ case EFFECT_GUARD_SPLIT:
}
break;
case EFFECT_PLEDGE:
if (isDoubleBattle && HasMoveWithEffect(BATTLE_PARTNER(battlerAtk), EFFECT_PLEDGE))
if (hasPartner && HasMoveWithEffect(BATTLE_PARTNER(battlerAtk), EFFECT_PLEDGE))
ADJUST_SCORE(GOOD_EFFECT); // Partner might use pledge move
break;
case EFFECT_TRICK_ROOM:
@ -5030,7 +5047,7 @@ case EFFECT_GUARD_SPLIT:
ADJUST_SCORE(WEAK_EFFECT);
if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_NONE && aiData->holdEffects[battlerDef] != HOLD_EFFECT_NONE)
ADJUST_SCORE(WEAK_EFFECT);
if (isDoubleBattle && aiData->holdEffects[BATTLE_PARTNER(battlerAtk)] == HOLD_EFFECT_NONE && aiData->holdEffects[BATTLE_PARTNER(battlerDef)] != HOLD_EFFECT_NONE)
if (!isBattle1v1 && aiData->holdEffects[BATTLE_PARTNER(battlerAtk)] == HOLD_EFFECT_NONE && aiData->holdEffects[BATTLE_PARTNER(battlerDef)] != HOLD_EFFECT_NONE)
ADJUST_SCORE(WEAK_EFFECT);
break;
case EFFECT_WONDER_ROOM:
@ -5125,7 +5142,7 @@ case EFFECT_GUARD_SPLIT:
ADJUST_SCORE(BEST_EFFECT);
break;
case EFFECT_QUASH:
if (isDoubleBattle && AI_IsSlower(BATTLE_PARTNER(battlerAtk), battlerDef, aiData->partnerMove, predictedMove, CONSIDER_PRIORITY))
if (hasPartner && AI_IsSlower(BATTLE_PARTNER(battlerAtk), battlerDef, aiData->partnerMove, predictedMove, CONSIDER_PRIORITY))
ADJUST_SCORE(DECENT_EFFECT); // Attacker partner wouldn't go before target
break;
case EFFECT_TAILWIND:
@ -5133,7 +5150,7 @@ case EFFECT_GUARD_SPLIT:
ADJUST_SCORE(GOOD_EFFECT);
break;
case EFFECT_LUCKY_CHANT:
if (!isDoubleBattle && CountUsablePartyMons(battlerDef) > 0)
if (isBattle1v1 && CountUsablePartyMons(battlerDef) > 0)
ADJUST_SCORE(GOOD_EFFECT);
break;
case EFFECT_MAGNET_RISE:
@ -5431,7 +5448,7 @@ case EFFECT_GUARD_SPLIT:
IncreasePoisonScore(battlerAtk, battlerDef, move, &score);
break;
case MOVE_EFFECT_CLEAR_SMOG:
score += AI_TryToClearStats(battlerAtk, battlerDef, FALSE);
score += AI_TryToClearStats(battlerAtk, battlerDef, moveTargetsBothOpponents);
break;
case MOVE_EFFECT_BUG_BITE: // And pluck
if (gBattleMons[battlerDef].volatiles.substitute || aiData->abilities[battlerDef] == ABILITY_STICKY_HOLD)
@ -5710,6 +5727,31 @@ static s32 AI_TryTo2HKO(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
return score;
}
// Adds score bonus to targeting "partner"
static s32 AI_AttacksPartner(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
{
if (battlerDef == BATTLE_PARTNER(battlerAtk)
// natural enemies in wild battles try to kill each other
&& ((IsNaturalEnemy(gBattleMons[battlerAtk].species, gBattleMons[battlerDef].species) && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_TRAINER)))
|| AI_FLAG_ATTACKS_PARTNER_FOCUSES_PARTNER))
{
u32 movesetIndex = gAiThinkingStruct->movesetIndex;
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, AI_ATTACKING))
ADJUST_SCORE(BEST_EFFECT);
u32 hitsToKO = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, gAiThinkingStruct->movesetIndex, AI_ATTACKING);
if (GetMoveTarget(move) == MOVE_TARGET_FOES_AND_ALLY && hitsToKO > 0 &&
(GetNoOfHitsToKOBattler(battlerAtk, FOE(battlerAtk), gAiThinkingStruct->movesetIndex, AI_ATTACKING) > 0 || GetNoOfHitsToKOBattler(battlerAtk, FOE(battlerDef), gAiThinkingStruct->movesetIndex, AI_ATTACKING) > 0))
ADJUST_SCORE(BEST_EFFECT);
if (hitsToKO > 0)
ADJUST_SCORE(DECENT_EFFECT);
}
return score;
}
// Prefers moves that are good for baton pass
static s32 AI_PreferBatonPass(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
{

View File

@ -1337,7 +1337,7 @@ bool32 CanEndureHit(u32 battler, u32 battlerTarget, u32 move)
enum BattleMoveEffects effect = GetMoveEffect(move);
if (!AI_BattlerAtMaxHp(battlerTarget) || effect == EFFECT_MULTI_HIT)
return FALSE;
if (GetMoveStrikeCount(move) > 1 && !(effect == EFFECT_DRAGON_DARTS && IsValidDoubleBattle(battlerTarget)))
if (GetMoveStrikeCount(move) > 1 && !(effect == EFFECT_DRAGON_DARTS && !HasTwoOpponents(battler)))
return FALSE;
if (gAiLogicData->holdEffects[battlerTarget] == HOLD_EFFECT_FOCUS_SASH)
return TRUE;
@ -1947,7 +1947,7 @@ s32 ProtectChecks(u32 battlerAtk, u32 battlerDef, u32 move, u32 predictedMove)
}
else
{
if (IsDoubleBattle())
if (!IsBattle1v1())
score -= (2 * min(uses, 3));
else
score -= (min(uses, 3));
@ -2225,7 +2225,7 @@ bool32 HasBattlerSideMoveWithEffect(u32 battler, u32 effect)
{
if (HasMoveWithEffect(battler, effect))
return TRUE;
if (IsDoubleBattle() && HasMoveWithEffect(BATTLE_OPPOSITE(battler), effect))
if (HasPartnerIgnoreFlags(battler) && HasMoveWithEffect(BATTLE_PARTNER(battler), effect))
return TRUE;
return FALSE;
}
@ -2240,7 +2240,7 @@ bool32 HasBattlerSideUsedMoveWithEffect(u32 battler, u32 effect)
{
if (GetMoveEffect(gBattleHistory->usedMoves[battler][i]) == effect)
return TRUE;
if (IsDoubleBattle() && GetMoveEffect(gBattleHistory->usedMoves[BATTLE_OPPOSITE(battler)][i]) == effect)
if (HasPartnerIgnoreFlags(battler) && GetMoveEffect(gBattleHistory->usedMoves[BATTLE_PARTNER(battler)][i]) == effect)
return TRUE;
}
return FALSE;
@ -2295,7 +2295,7 @@ bool32 HasBattlerSideMoveWithAdditionalEffect(u32 battler, u32 moveEffect)
{
if (HasMoveWithAdditionalEffect(battler, moveEffect))
return TRUE;
if (IsDoubleBattle() && HasMoveWithAdditionalEffect(BATTLE_OPPOSITE(battler), moveEffect))
if (HasPartnerIgnoreFlags(battler) && HasMoveWithAdditionalEffect(BATTLE_PARTNER(battler), moveEffect))
return TRUE;
return FALSE;
}
@ -2310,7 +2310,7 @@ bool32 HasBattlerSideUsedMoveWithAdditionalEffect(u32 battler, u32 moveEffect)
{
if (MoveHasAdditionalEffect(gBattleHistory->usedMoves[battler][i], moveEffect))
return TRUE;
if (IsDoubleBattle() && MoveHasAdditionalEffect(gBattleHistory->usedMoves[BATTLE_OPPOSITE(battler)][i], moveEffect))
if (HasPartnerIgnoreFlags(battler) && MoveHasAdditionalEffect(gBattleHistory->usedMoves[BATTLE_PARTNER(battler)][i], moveEffect))
return TRUE;
}
return FALSE;
@ -3073,7 +3073,7 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 mov
if (PartyBattlerShouldAvoidHazards(battlerAtk, battlerToSwitch))
return DONT_PIVOT;
if (!IsDoubleBattle())
if (IsBattle1v1())
{
if (CountUsablePartyMons(battlerAtk) == 0)
return CAN_TRY_PIVOT; // can't switch, but attack might still be useful
@ -3543,7 +3543,7 @@ bool32 AnyPartyMemberStatused(u32 battlerId, bool32 checkSoundproof)
party = GetBattlerParty(battlerId);
if (IsDoubleBattle())
if (HasPartner(battlerId))
{
battlerOnField1 = gBattlerPartyIndexes[battlerId];
battlerOnField2 = gBattlerPartyIndexes[GetPartnerBattler(battlerId)];
@ -3595,7 +3595,7 @@ u32 GetBattlerSideSpeedAverage(u32 battler)
numBattlersAlive++;
}
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battler)))
if (HasPartner(battler))
{
speed2 = gAiLogicData->speedStats[BATTLE_PARTNER(battler)];
numBattlersAlive++;
@ -3709,17 +3709,48 @@ bool32 ShouldSetScreen(u32 battlerAtk, u32 battlerDef, enum BattleMoveEffects mo
}
// Partner Logic
bool32 IsValidDoubleBattle(u32 battlerAtk)
bool32 IsBattle1v1()
{
if (IsDoubleBattle()
&& ((IsBattlerAlive(BATTLE_OPPOSITE(battlerAtk)) && IsBattlerAlive(BATTLE_PARTNER(BATTLE_OPPOSITE(battlerAtk)))) || IsBattlerAlive(BATTLE_PARTNER(battlerAtk))))
&& ((IsBattlerAlive(B_POSITION_PLAYER_LEFT) && IsBattlerAlive(B_POSITION_PLAYER_RIGHT))
|| (IsBattlerAlive(B_POSITION_OPPONENT_LEFT) && IsBattlerAlive(B_POSITION_OPPONENT_RIGHT))))
return FALSE;
return TRUE;
}
bool32 HasTwoOpponents(u32 battler)
{
if (IsDoubleBattle()
&& IsBattlerAlive(FOE(battler)) && IsBattlerAlive(BATTLE_PARTNER(FOE(battler))))
return TRUE;
return FALSE;
}
// TODO: Handling for when the 'partner' is not actually a partner, a la Battle Royale or B_WILD_NATURAL_ENEMIES
bool32 HasPartner(u32 battler)
{
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battler)))
{
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_ATTACKS_PARTNER)
return FALSE;
else
return TRUE;
}
return FALSE;
}
bool32 HasPartnerIgnoreFlags(u32 battler)
{
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battler)))
{
return TRUE;
}
return FALSE;
}
bool32 IsTargetingPartner(u32 battlerAtk, u32 battlerDef)
{
if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_ATTACKS_PARTNER)
return FALSE;
return ((battlerAtk) == (battlerDef ^ BIT_FLANK));
}
@ -3738,7 +3769,7 @@ u32 GetAllyChosenMove(u32 battlerId)
//PARTNER_MOVE_EFFECT_IS_SAME
bool32 DoesPartnerHaveSameMoveEffect(u32 battlerAtkPartner, u32 battlerDef, u32 move, u32 partnerMove)
{
if (!IsDoubleBattle())
if (!HasPartner(battlerAtkPartner))
return FALSE;
if (GetMoveEffect(move) == GetMoveEffect(partnerMove)
@ -3753,7 +3784,7 @@ bool32 DoesPartnerHaveSameMoveEffect(u32 battlerAtkPartner, u32 battlerDef, u32
//PARTNER_MOVE_EFFECT_IS_SAME_NO_TARGET
bool32 PartnerHasSameMoveEffectWithoutTarget(u32 battlerAtkPartner, u32 move, u32 partnerMove)
{
if (!IsDoubleBattle())
if (!HasPartner(battlerAtkPartner))
return FALSE;
if (GetMoveEffect(move) == GetMoveEffect(partnerMove)
@ -3765,7 +3796,7 @@ bool32 PartnerHasSameMoveEffectWithoutTarget(u32 battlerAtkPartner, u32 move, u3
//PARTNER_MOVE_EFFECT_IS_STATUS_SAME_TARGET
bool32 PartnerMoveEffectIsStatusSameTarget(u32 battlerAtkPartner, u32 battlerDef, u32 partnerMove)
{
if (!IsDoubleBattle())
if (!HasPartner(battlerAtkPartner))
return FALSE;
enum BattleMoveEffects partnerEffect = GetMoveEffect(partnerMove);
@ -3799,7 +3830,7 @@ bool32 IsMoveEffectWeather(u32 move)
//PARTNER_MOVE_EFFECT_IS_TERRAIN
bool32 PartnerMoveEffectIsTerrain(u32 battlerAtkPartner, u32 partnerMove)
{
if (!IsDoubleBattle())
if (!HasPartner(battlerAtkPartner))
return FALSE;
enum BattleMoveEffects partnerEffect = GetMoveEffect(partnerMove);
@ -3816,7 +3847,7 @@ bool32 PartnerMoveEffectIsTerrain(u32 battlerAtkPartner, u32 partnerMove)
//PARTNER_MOVE_EFFECT_IS
bool32 PartnerMoveEffectIs(u32 battlerAtkPartner, u32 partnerMove, enum BattleMoveEffects effectCheck)
{
if (!IsDoubleBattle())
if (!HasPartner(battlerAtkPartner))
return FALSE;
if (partnerMove != MOVE_NONE && GetMoveEffect(partnerMove) == effectCheck)
@ -3828,7 +3859,7 @@ bool32 PartnerMoveEffectIs(u32 battlerAtkPartner, u32 partnerMove, enum BattleMo
//PARTNER_MOVE_IS_TAILWIND_TRICKROOM
bool32 PartnerMoveIs(u32 battlerAtkPartner, u32 partnerMove, u32 moveCheck)
{
if (!IsDoubleBattle())
if (!HasPartner(battlerAtkPartner))
return FALSE;
if (partnerMove != MOVE_NONE && partnerMove == moveCheck)
@ -3839,7 +3870,7 @@ bool32 PartnerMoveIs(u32 battlerAtkPartner, u32 partnerMove, u32 moveCheck)
//PARTNER_MOVE_IS_SAME
bool32 PartnerMoveIsSameAsAttacker(u32 battlerAtkPartner, u32 battlerDef, u32 move, u32 partnerMove)
{
if (!IsDoubleBattle())
if (!HasPartner(battlerAtkPartner))
return FALSE;
if (partnerMove != MOVE_NONE && move == partnerMove && gBattleStruct->moveTarget[battlerAtkPartner] == battlerDef)
@ -3850,7 +3881,7 @@ bool32 PartnerMoveIsSameAsAttacker(u32 battlerAtkPartner, u32 battlerDef, u32 mo
//PARTNER_MOVE_IS_SAME_NO_TARGET
bool32 PartnerMoveIsSameNoTarget(u32 battlerAtkPartner, u32 move, u32 partnerMove)
{
if (!IsDoubleBattle())
if (!HasPartner(battlerAtkPartner))
return FALSE;
if (partnerMove != MOVE_NONE && move == partnerMove)
return TRUE;
@ -3859,7 +3890,7 @@ bool32 PartnerMoveIsSameNoTarget(u32 battlerAtkPartner, u32 move, u32 partnerMov
bool32 PartnerMoveActivatesSleepClause(u32 partnerMove)
{
if (!IsDoubleBattle() || !IsSleepClauseEnabled())
if (IsBattle1v1() || !IsSleepClauseEnabled())
return FALSE;
return IsMoveSleepClauseTrigger(partnerMove);
}
@ -3894,7 +3925,7 @@ bool32 ShouldUseWishAromatherapy(u32 battlerAtk, u32 battlerDef, u32 move)
}
}
if (!IsDoubleBattle())
if (IsBattle1v1())
{
switch (GetMoveEffect(move))
{
@ -4086,7 +4117,7 @@ bool32 PartyHasMoveCategory(u32 battlerId, enum DamageCategory category)
bool32 SideHasMoveCategory(u32 battlerId, enum DamageCategory category)
{
if (IsDoubleBattle())
if (HasPartnerIgnoreFlags(battlerId))
{
if (HasMoveWithCategory(battlerId, category) || HasMoveWithCategory(BATTLE_PARTNER(battlerId), category))
return TRUE;
@ -4612,7 +4643,7 @@ void DecideTerastal(u32 battler)
return;
// TODO: Currently only single battles are considered.
if (IsDoubleBattle())
if (!IsBattle1v1())
return;
// TODO: A lot of these checks are most effective for an omnicient ai.
@ -5030,7 +5061,7 @@ bool32 HasBattlerSideAbility(u32 battler, u32 ability, struct AiLogicData *aiDat
{
if (aiData->abilities[battler] == ability)
return TRUE;
if (IsDoubleBattle() && gAiLogicData->abilities[BATTLE_PARTNER(battler)] == ability)
if (HasPartnerIgnoreFlags(battler) && gAiLogicData->abilities[BATTLE_PARTNER(battler)] == ability)
return TRUE;
return FALSE;
}
@ -5041,6 +5072,8 @@ u32 GetFriendlyFireKOThreshold(u32 battler)
return FRIENDLY_FIRE_RISKY_THRESHOLD;
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_CONSERVATIVE)
return FRIENDLY_FIRE_CONSERVATIVE_THRESHOLD;
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_ATTACKS_PARTNER)
return 0;
return FRIENDLY_FIRE_NORMAL_THRESHOLD;
}
@ -5192,7 +5225,7 @@ bool32 CanEffectChangeAbility(u32 battlerAtk, u32 battlerDef, u32 effect, struct
if (hasSameAbility || gAbilitiesInfo[atkAbility].cantBeSuppressed || gAbilitiesInfo[defAbility].cantBeCopied)
return FALSE;
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))
if (HasPartnerIgnoreFlags(battlerAtk))
{
u32 partnerAbility = aiData->abilities[BATTLE_PARTNER(battlerAtk)];
if (gAbilitiesInfo[partnerAbility].cantBeSuppressed)
@ -5293,7 +5326,7 @@ void AbilityChangeScore(u32 battlerAtk, u32 battlerDef, u32 effect, s32 *score,
bool32 attackerHasBadAbility = (gAbilitiesInfo[abilityAtk].aiRating < 0);
s32 currentAbilityScore, transferredAbilityScore = 0;
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))
if (HasPartner(battlerAtk))
{
partnerAbility = aiData->abilities[BATTLE_PARTNER(battlerAtk)];
if (!(gAbilitiesInfo[partnerAbility].cantBeSuppressed) && (gAbilitiesInfo[partnerAbility].aiRating < 0))
@ -5387,7 +5420,7 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData
case ABILITY_FRIEND_GUARD:
case ABILITY_POWER_SPOT:
case ABILITY_VICTORY_STAR:
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battler)) && aiData->abilities[BATTLE_PARTNER(battler)] != ability)
if (HasPartner(battler) && aiData->abilities[BATTLE_PARTNER(battler)] != ability)
return BEST_EFFECT;
break;
case ABILITY_GUTS:
@ -5414,7 +5447,7 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData
}
else
{
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(FOE(battler))))
if (HasTwoOpponents(battler))
{
abilityDef = aiData->abilities[BATTLE_PARTNER(FOE(battler))];
if (DoesIntimidateRaiseStats(abilityDef))
@ -5457,7 +5490,7 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData
case ABILITY_SWORD_OF_RUIN:
case ABILITY_TABLETS_OF_RUIN:
case ABILITY_VESSEL_OF_RUIN:
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battler)))
if (HasPartner(battler))
{
if (aiData->abilities[BATTLE_PARTNER(battler)] != ability)
return GOOD_EFFECT;
@ -5480,3 +5513,28 @@ u32 GetThinkingBattler(u32 battler)
return gAiLogicData->battlerDoingPrediction;
return battler;
}
bool32 IsNaturalEnemy(u32 speciesAttacker, u32 speciesTarget)
{
if (B_WILD_NATURAL_ENEMIES != TRUE)
return FALSE;
switch (speciesAttacker)
{
case SPECIES_ZANGOOSE:
return (speciesTarget == SPECIES_SEVIPER);
case SPECIES_SEVIPER:
return (speciesTarget == SPECIES_ZANGOOSE);
case SPECIES_HEATMOR:
return (speciesTarget == SPECIES_DURANT);
case SPECIES_DURANT:
return (speciesTarget == SPECIES_HEATMOR);
case SPECIES_SABLEYE:
return (speciesTarget == SPECIES_CARBINK);
case SPECIES_MAREANIE:
return (speciesTarget == SPECIES_CORSOLA);
default:
return FALSE;
}
return FALSE;
}

View File

@ -473,31 +473,15 @@ static void OpponentHandleChooseMove(u32 battler)
target = GetBattlerAtPosition(Random() & 2);
} while (!CanTargetBattler(battler, target, move));
// Don't bother to loop through table if the move can't attack ally
// Don't bother to check if they're enemies if the move can't attack ally
if (B_WILD_NATURAL_ENEMIES == TRUE && !(GetBattlerMoveTargetType(battler, move) & MOVE_TARGET_BOTH))
{
u16 i, speciesAttacker, speciesTarget, isPartnerEnemy = FALSE;
static const u16 naturalEnemies[][2] =
{
// Attacker Target
{SPECIES_ZANGOOSE, SPECIES_SEVIPER},
{SPECIES_SEVIPER, SPECIES_ZANGOOSE},
{SPECIES_HEATMOR, SPECIES_DURANT},
{SPECIES_DURANT, SPECIES_HEATMOR},
{SPECIES_SABLEYE, SPECIES_CARBINK},
{SPECIES_MAREANIE, SPECIES_CORSOLA},
};
u32 speciesAttacker, speciesTarget;
speciesAttacker = gBattleMons[battler].species;
speciesTarget = gBattleMons[GetBattlerAtPosition(BATTLE_PARTNER(battler))].species;
for (i = 0; i < ARRAY_COUNT(naturalEnemies); i++)
{
if (speciesAttacker == naturalEnemies[i][0] && speciesTarget == naturalEnemies[i][1])
{
isPartnerEnemy = TRUE;
break;
}
}
bool32 isPartnerEnemy = IsNaturalEnemy(speciesAttacker, speciesTarget);
if (isPartnerEnemy && CanTargetBattler(battler, target, move))
BtlController_EmitTwoReturnValues(battler, B_COMM_TO_ENGINE, B_ACTION_EXEC_SCRIPT, (chosenMoveIndex) | (GetBattlerAtPosition(BATTLE_PARTNER(battler)) << 8));
else

View File

@ -510,7 +510,7 @@ AI_DOUBLE_BATTLE_TEST("AI sets up weather for its ally")
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_TORNADUS) { Item(ITEM_SAFETY_GOGGLES); Ability(ABILITY_PRANKSTER); Moves(goodWeather, badWeather, MOVE_RETURN, MOVE_TAUNT); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(weatherTrigger, MOVE_EARTH_POWER); }
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SAFETY_GOGGLES); Moves(weatherTrigger, MOVE_EARTH_POWER); }
} WHEN {
TURN { EXPECT_MOVE(opponentLeft, goodWeather); }
}

View File

@ -0,0 +1,78 @@
#include "global.h"
#include "test/battle.h"
#include "battle_ai_util.h"
AI_DOUBLE_BATTLE_TEST("AI_FLAG_ATTACKS_PARTNER is willing to kill either the partner or the player")
{
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
u32 move, level;
PARAMETRIZE { move = MOVE_BRUTAL_SWING; level = 1; }
PARAMETRIZE { move = MOVE_MIGHTY_CLEAVE; level = 1; }
PARAMETRIZE { move = MOVE_BRUTAL_SWING; level = 100; }
PARAMETRIZE { move = MOVE_MIGHTY_CLEAVE; level = 100; }
PARAMETRIZE { move = MOVE_BRUTAL_SWING; level = 50; }
PARAMETRIZE { move = MOVE_MIGHTY_CLEAVE; level = 50; }
GIVEN {
ASSUME(GetMovePower(MOVE_OVERDRIVE) == 80);
ASSUME(GetMovePower(MOVE_BRUTAL_SWING) == 60);
ASSUME(GetMovePower(MOVE_MIGHTY_CLEAVE) == 95);
ASSUME(gSpeciesInfo[SPECIES_ZIGZAGOON].baseAttack == gSpeciesInfo[SPECIES_ZIGZAGOON].baseSpAttack);
ASSUME(gSpeciesInfo[SPECIES_ZIGZAGOON].baseDefense == gSpeciesInfo[SPECIES_ZIGZAGOON].baseSpDefense);
ASSUME(gSpeciesInfo[SPECIES_ZIGZAGOON].baseHP == 38);
ASSUME(gSpeciesInfo[SPECIES_ZIGZAGOON].baseAttack == 30);
ASSUME(gSpeciesInfo[SPECIES_ZIGZAGOON].baseDefense == 41);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_ATTACKS_PARTNER);
PLAYER(SPECIES_ZIGZAGOON) { Level(50); }
PLAYER(SPECIES_ZIGZAGOON) { Level(16); }
OPPONENT(SPECIES_ZIGZAGOON) { Level(50); Moves(move, MOVE_OVERDRIVE, MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Level(level); Moves(MOVE_CELEBRATE); }
} WHEN {
TURN
{
if (move == MOVE_MIGHTY_CLEAVE)
{
if (level == 1)
EXPECT_MOVE(opponentLeft, move, target: opponentRight);
else
EXPECT_MOVE(opponentLeft, move, target: playerRight);
}
else
{
if (level == 1 || AI_FLAG_ATTACKS_PARTNER_FOCUSES_PARTNER)
EXPECT_MOVE(opponentLeft, move);
else
EXPECT_MOVE(opponentLeft, MOVE_OVERDRIVE);
}
}
}
}
AI_DOUBLE_BATTLE_TEST("AI_FLAG_ATTACKS_PARTNER steps on its ally's weather")
{
u32 weather1, move1, weather2, move2;
PARAMETRIZE { weather1 = MOVE_SUNNY_DAY; move1 = MOVE_SOLAR_BEAM; weather2 = MOVE_RAIN_DANCE; move2 = MOVE_THUNDER; }
PARAMETRIZE { weather1 = MOVE_RAIN_DANCE; move1 = MOVE_THUNDER; weather2 = MOVE_SUNNY_DAY; move2 = MOVE_SOLAR_BEAM; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_ATTACKS_PARTNER);
PLAYER(SPECIES_WOBBUFFET) { Speed(50); }
PLAYER(SPECIES_WOBBUFFET) { Speed(50); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(75); Moves(weather1, move1, MOVE_HEADBUTT); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(100); Moves(weather2, move2, MOVE_HEADBUTT); }
} WHEN {
TURN
{
EXPECT_MOVE(opponentLeft, weather1);
EXPECT_MOVE(opponentRight, weather2);
}
TURN
{
EXPECT_MOVE(opponentLeft, move1);
EXPECT_MOVE(opponentRight, weather2);
}
}
}