AI double battle friendly fire logic improvements (#6766)
This commit is contained in:
parent
d1307371db
commit
e44d0326be
@ -197,6 +197,10 @@ bool32 ShouldTrap(u32 battlerAtk, u32 battlerDef, u32 move);
|
||||
bool32 IsWakeupTurn(u32 battler);
|
||||
bool32 AI_IsBattlerAsleepOrComatose(u32 battlerId);
|
||||
|
||||
// ability logic
|
||||
bool32 IsMoxieTypeAbility(u32 ability);
|
||||
bool32 ShouldTriggerAbility(u32 battler, u32 ability);
|
||||
|
||||
// partner logic
|
||||
#define IS_TARGETING_PARTNER(battlerAtk, battlerDef)((battlerAtk) == (battlerDef ^ BIT_FLANK))
|
||||
u32 GetAllyChosenMove(u32 battlerId);
|
||||
@ -212,6 +216,8 @@ bool32 PartnerMoveIsSameAsAttacker(u32 battlerAtkPartner, u32 battlerDef, u32 mo
|
||||
bool32 PartnerMoveIsSameNoTarget(u32 battlerAtkPartner, u32 move, u32 partnerMove);
|
||||
bool32 PartnerMoveActivatesSleepClause(u32 move);
|
||||
bool32 ShouldUseWishAromatherapy(u32 battlerAtk, u32 battlerDef, u32 move);
|
||||
u32 GetFriendlyFireKOThreshold(u32 battler);
|
||||
bool32 IsAllyProtectingFromMove(u32 battlerAtk, u32 attackerMove, u32 allyMove);
|
||||
|
||||
// party logic
|
||||
struct BattlePokemon *AllocSaveBattleMons(void);
|
||||
|
||||
@ -69,4 +69,9 @@
|
||||
// Score reduction if any roll for PP stall detection passes
|
||||
#define PP_STALL_SCORE_REDUCTION 20
|
||||
|
||||
// AI's acceptable number of hits to KO the partner via friendly fire in a double battle.
|
||||
#define FRIENDLY_FIRE_RISKY_THRESHOLD 2
|
||||
#define FRIENDLY_FIRE_NORMAL_THRESHOLD 3
|
||||
#define FRIENDLY_FIRE_CONSERVATIVE_THRESHOLD 4
|
||||
|
||||
#endif // GUARD_CONFIG_AI_H
|
||||
|
||||
@ -1110,7 +1110,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
||||
RETURN_SCORE_MINUS(20);
|
||||
break;
|
||||
case ABILITY_JUSTIFIED:
|
||||
if (moveType == TYPE_DARK && !IsBattleMoveStatus(move))
|
||||
if (moveType == TYPE_DARK && !IsBattleMoveStatus(move) && !IS_TARGETING_PARTNER(battlerAtk, battlerDef))
|
||||
RETURN_SCORE_MINUS(10);
|
||||
break;
|
||||
case ABILITY_RATTLED:
|
||||
@ -2221,8 +2221,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
||||
&& move != MOVE_CRAFTY_SHIELD) //These moves have infinite usage
|
||||
{
|
||||
if (GetBattlerSecondaryDamage(battlerAtk) >= gBattleMons[battlerAtk].hp
|
||||
&& aiData->abilities[battlerDef] != ABILITY_MOXIE
|
||||
&& aiData->abilities[battlerDef] != ABILITY_BEAST_BOOST)
|
||||
&& !(IsMoxieTypeAbility(aiData->abilities[battlerDef])))
|
||||
{
|
||||
ADJUST_SCORE(-10); //Don't protect if you're going to faint after protecting
|
||||
}
|
||||
@ -2929,7 +2928,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
||||
u32 atkPartnerAbility = aiData->abilities[BATTLE_PARTNER(battlerAtk)];
|
||||
u32 atkPartnerHoldEffect = aiData->holdEffects[BATTLE_PARTNER(battlerAtk)];
|
||||
enum BattleMoveEffects partnerEffect = GetMoveEffect(aiData->partnerMove);
|
||||
bool32 partnerProtecting = (partnerEffect == EFFECT_PROTECT);
|
||||
bool32 partnerProtecting = IsAllyProtectingFromMove(battlerAtk, move, aiData->partnerMove) && !MoveIgnoresProtect(move);
|
||||
bool32 attackerHasBadAbility = (gAbilitiesInfo[aiData->abilities[battlerAtk]].aiRating < 0);
|
||||
bool32 partnerHasBadAbility = (gAbilitiesInfo[atkPartnerAbility].aiRating < 0);
|
||||
u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData);
|
||||
@ -2937,6 +2936,12 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
||||
SetTypeBeforeUsingMove(move, battlerAtk);
|
||||
moveType = GetBattleMoveType(move);
|
||||
|
||||
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 isFriendlyFireOK = !wouldPartnerFaint && (noOfHitsToKOPartner == 0 || noOfHitsToKOPartner > friendlyFireThreshold);
|
||||
|
||||
// check what effect partner is using
|
||||
if (aiData->partnerMove != 0)
|
||||
{
|
||||
@ -2987,7 +2992,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
||||
{
|
||||
case EFFECT_HELPING_HAND:
|
||||
if (!IsBattlerAlive(battlerAtkPartner) || !HasDamagingMove(battlerAtkPartner))
|
||||
ADJUST_SCORE(-5);
|
||||
ADJUST_SCORE(-20);
|
||||
break;
|
||||
case EFFECT_PERISH_SONG:
|
||||
if (aiData->partnerMove != 0 && HasTrappingMoveEffect(battlerAtkPartner))
|
||||
@ -3054,9 +3059,103 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
||||
break;
|
||||
} // global move effect check
|
||||
|
||||
// Specific logic for spread moves.
|
||||
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
|
||||
{
|
||||
// Don't kill your partner for no reason.
|
||||
if (wouldPartnerFaint)
|
||||
{
|
||||
// If it kills both opponents, that's a good reason.
|
||||
if (CanIndexMoveFaintTarget(battlerAtk, BATTLE_OPPOSITE(battlerAtk), gAiThinkingStruct->movesetIndex, AI_ATTACKING)
|
||||
&& CanIndexMoveFaintTarget(battlerAtk, BATTLE_OPPOSITE(battlerAtkPartner), gAiThinkingStruct->movesetIndex, AI_ATTACKING))
|
||||
{
|
||||
switch (effect)
|
||||
{
|
||||
case EFFECT_EXPLOSION:
|
||||
if (gAiThinkingStruct->aiFlags[battlerAtk] & (AI_FLAG_RISKY | AI_FLAG_WILL_SUICIDE))
|
||||
{
|
||||
RETURN_SCORE_PLUS(10);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
RETURN_SCORE_PLUS(10);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Both opponents can kill the partner
|
||||
else if (CanTargetFaintAi(BATTLE_OPPOSITE(battlerAtk), battlerAtkPartner)
|
||||
&& (CanTargetFaintAi(BATTLE_OPPOSITE(battlerAtkPartner), battlerAtkPartner)))
|
||||
{
|
||||
// The spread move should kill an opponent.
|
||||
if (CanIndexMoveFaintTarget(battlerAtk, BATTLE_OPPOSITE(battlerAtk), gAiThinkingStruct->movesetIndex, AI_ATTACKING)
|
||||
|| CanIndexMoveFaintTarget(battlerAtk, BATTLE_OPPOSITE(battlerAtkPartner), gAiThinkingStruct->movesetIndex, AI_ATTACKING))
|
||||
{
|
||||
ADJUST_SCORE(WEAK_EFFECT);
|
||||
}
|
||||
|
||||
// Alternatively, it benefits from the ally's death, and it will probably die anyway.
|
||||
if (IsMoxieTypeAbility(aiData->abilities[battlerAtk]))
|
||||
{
|
||||
ADJUST_SCORE(GOOD_EFFECT);
|
||||
}
|
||||
if ((aiData->abilities[battlerAtk] == ABILITY_RECEIVER) && !partnerHasBadAbility)
|
||||
{
|
||||
ADJUST_SCORE(GOOD_EFFECT);
|
||||
}
|
||||
if (HasMove(battlerAtk, MOVE_LAST_RESPECTS))
|
||||
{
|
||||
ADJUST_SCORE(GOOD_EFFECT);
|
||||
}
|
||||
}
|
||||
// It can kill one opponent when killing its ally, and its ally is not in extreme danger.
|
||||
// This is easy for the player to cheese.
|
||||
else if (CanIndexMoveFaintTarget(battlerAtk, BATTLE_OPPOSITE(battlerAtk), gAiThinkingStruct->movesetIndex, AI_ATTACKING)
|
||||
|| CanIndexMoveFaintTarget(battlerAtk, BATTLE_OPPOSITE(battlerAtkPartner), gAiThinkingStruct->movesetIndex, AI_ATTACKING))
|
||||
{
|
||||
if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_WILL_SUICIDE)
|
||||
{
|
||||
RETURN_SCORE_PLUS(10);
|
||||
}
|
||||
else
|
||||
{
|
||||
ADJUST_SCORE(-DECENT_EFFECT);
|
||||
}
|
||||
}
|
||||
// No reason to kill partner has been found.
|
||||
else
|
||||
{
|
||||
RETURN_SCORE_MINUS(10);
|
||||
}
|
||||
}
|
||||
|
||||
// Partner will not faint.
|
||||
else {
|
||||
|
||||
// Triggering your ally's hold item should only be done deliberately with a spread move.
|
||||
switch (atkPartnerHoldEffect)
|
||||
{
|
||||
case HOLD_EFFECT_WEAKNESS_POLICY:
|
||||
if (aiData->effectiveness[battlerAtk][battlerAtkPartner][gAiThinkingStruct->movesetIndex] >= UQ_4_12(2.0) && isFriendlyFireOK)
|
||||
{
|
||||
ADJUST_SCORE(GOOD_EFFECT);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check specific target
|
||||
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
|
||||
{
|
||||
bool32 isMoveAffectedByPartnerAbility = TRUE;
|
||||
|
||||
if (wouldPartnerFaint)
|
||||
{
|
||||
RETURN_SCORE_MINUS(30);
|
||||
}
|
||||
|
||||
// partner ability checks
|
||||
if (!partnerProtecting && moveTarget != MOVE_TARGET_BOTH && !DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move))
|
||||
{
|
||||
@ -3066,95 +3165,241 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
||||
if (MoveAlwaysCrits(move)
|
||||
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)
|
||||
&& AI_IsFaster(battlerAtk, battlerAtkPartner, move)
|
||||
&& !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING))
|
||||
&& isFriendlyFireOK)
|
||||
{
|
||||
RETURN_SCORE_PLUS(GOOD_EFFECT);
|
||||
if (MoveAlwaysCrits(move))
|
||||
{
|
||||
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
|
||||
{
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
}
|
||||
RETURN_SCORE_PLUS(GOOD_EFFECT);
|
||||
}
|
||||
// todo: figuring out a non-guaranteed crit roll that's useful
|
||||
}
|
||||
break;
|
||||
case ABILITY_VOLT_ABSORB:
|
||||
if (!(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_HP_AWARE))
|
||||
else
|
||||
{
|
||||
RETURN_SCORE_MINUS(10);
|
||||
}
|
||||
break; // handled in AI_HPAware
|
||||
case ABILITY_MOTOR_DRIVE:
|
||||
if (moveType == TYPE_ELECTRIC && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPEED))
|
||||
{
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
isMoveAffectedByPartnerAbility = FALSE;
|
||||
}
|
||||
break;
|
||||
case ABILITY_LIGHTNING_ROD:
|
||||
if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5
|
||||
&& moveType == TYPE_ELECTRIC
|
||||
&& HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_SPECIAL)
|
||||
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPATK))
|
||||
case ABILITY_MOTOR_DRIVE:
|
||||
case ABILITY_VOLT_ABSORB:
|
||||
if (moveType == TYPE_ELECTRIC)
|
||||
{
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
if (B_REDIRECT_ABILITY_IMMUNITY < GEN_5 && atkPartnerAbility == ABILITY_LIGHTNING_ROD)
|
||||
{
|
||||
RETURN_SCORE_MINUS(10);
|
||||
}
|
||||
|
||||
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
|
||||
{
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
}
|
||||
else if (ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
|
||||
{
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
}
|
||||
else
|
||||
{
|
||||
RETURN_SCORE_MINUS(10);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isMoveAffectedByPartnerAbility = FALSE;
|
||||
}
|
||||
break;
|
||||
case ABILITY_WATER_ABSORB:
|
||||
case ABILITY_DRY_SKIN:
|
||||
case ABILITY_EARTH_EATER:
|
||||
if (!(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_HP_AWARE))
|
||||
case ABILITY_LEVITATE:
|
||||
if (moveType == TYPE_GROUND)
|
||||
{
|
||||
RETURN_SCORE_MINUS(10);
|
||||
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
|
||||
{
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
}
|
||||
else if (atkPartnerAbility == ABILITY_EARTH_EATER && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_HP_AWARE))
|
||||
{
|
||||
RETURN_SCORE_MINUS(10);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isMoveAffectedByPartnerAbility = FALSE;
|
||||
}
|
||||
break; // handled in AI_HPAware
|
||||
case ABILITY_DRY_SKIN:
|
||||
case ABILITY_WATER_ABSORB:
|
||||
case ABILITY_STORM_DRAIN:
|
||||
if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5
|
||||
&& moveType == TYPE_WATER
|
||||
&& HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_SPECIAL)
|
||||
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPATK))
|
||||
if (moveType == TYPE_WATER)
|
||||
{
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
if (B_REDIRECT_ABILITY_IMMUNITY < GEN_5 && atkPartnerAbility == ABILITY_STORM_DRAIN)
|
||||
{
|
||||
RETURN_SCORE_MINUS(10);
|
||||
}
|
||||
|
||||
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
|
||||
{
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
}
|
||||
else if (ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
|
||||
{
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
}
|
||||
else
|
||||
{
|
||||
RETURN_SCORE_MINUS(10);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isMoveAffectedByPartnerAbility = FALSE;
|
||||
}
|
||||
break;
|
||||
case ABILITY_WATER_COMPACTION:
|
||||
if (moveType == TYPE_WATER && GetNoOfHitsToKOBattler(battlerAtk, battlerDef, gAiThinkingStruct->movesetIndex, AI_ATTACKING) >= 4)
|
||||
if (moveType == TYPE_WATER && isFriendlyFireOK
|
||||
&& ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
|
||||
{
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT); // only mon with this ability is weak to water so only make it okay if we do very little damage
|
||||
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
|
||||
{
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
}
|
||||
|
||||
if (GetMoveStrikeCount(move) > 1 && effect != EFFECT_DRAGON_DARTS)
|
||||
{
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isMoveAffectedByPartnerAbility = FALSE;
|
||||
}
|
||||
break;
|
||||
case ABILITY_STEAM_ENGINE:
|
||||
if (isFriendlyFireOK && (moveType == TYPE_WATER || moveType == TYPE_FIRE)
|
||||
&& ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
|
||||
{
|
||||
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
|
||||
{
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
}
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
}
|
||||
else
|
||||
{
|
||||
isMoveAffectedByPartnerAbility = FALSE;
|
||||
}
|
||||
break;
|
||||
case ABILITY_THERMAL_EXCHANGE:
|
||||
if (moveType == TYPE_FIRE && isFriendlyFireOK
|
||||
&& !IsBattleMoveStatus(move)
|
||||
&& ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
|
||||
{
|
||||
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
|
||||
{
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
}
|
||||
|
||||
if (GetMoveStrikeCount(move) > 1 && effect != EFFECT_DRAGON_DARTS)
|
||||
{
|
||||
ADJUST_SCORE(WEAK_EFFECT);
|
||||
}
|
||||
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
}
|
||||
else
|
||||
{
|
||||
isMoveAffectedByPartnerAbility = FALSE;
|
||||
}
|
||||
RETURN_SCORE_MINUS(10);
|
||||
break;
|
||||
case ABILITY_FLASH_FIRE:
|
||||
if (moveType == TYPE_FIRE
|
||||
&& HasMoveWithType(battlerAtkPartner, TYPE_FIRE)
|
||||
&& !gDisableStructs[battlerAtkPartner].flashFireBoosted)
|
||||
case ABILITY_WELL_BAKED_BODY:
|
||||
if (moveType == TYPE_FIRE)
|
||||
{
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
|
||||
{
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
}
|
||||
if (ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
|
||||
{
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isMoveAffectedByPartnerAbility = FALSE;
|
||||
}
|
||||
break;
|
||||
case ABILITY_SAP_SIPPER:
|
||||
if (moveType == TYPE_GRASS
|
||||
&& HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL)
|
||||
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK))
|
||||
if (moveType == TYPE_GRASS)
|
||||
{
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
|
||||
{
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
}
|
||||
|
||||
if (ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
|
||||
{
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isMoveAffectedByPartnerAbility = FALSE;
|
||||
}
|
||||
break;
|
||||
case ABILITY_JUSTIFIED:
|
||||
if (moveType == TYPE_DARK
|
||||
if (moveType == TYPE_DARK && isFriendlyFireOK
|
||||
&& !IsBattleMoveStatus(move)
|
||||
&& HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL)
|
||||
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)
|
||||
&& !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING))
|
||||
&& ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
|
||||
{
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
|
||||
{
|
||||
ADJUST_SCORE(GOOD_EFFECT);
|
||||
}
|
||||
|
||||
if (GetMoveStrikeCount(move) > 1 && effect != EFFECT_DRAGON_DARTS)
|
||||
{
|
||||
ADJUST_SCORE(WEAK_EFFECT);
|
||||
}
|
||||
|
||||
ADJUST_SCORE(WEAK_EFFECT); // Beat Up is handled later
|
||||
}
|
||||
else
|
||||
{
|
||||
isMoveAffectedByPartnerAbility = FALSE;
|
||||
}
|
||||
break;
|
||||
case ABILITY_RATTLED:
|
||||
if (!IsBattleMoveStatus(move)
|
||||
if (!IsBattleMoveStatus(move) && isFriendlyFireOK
|
||||
&& (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG)
|
||||
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPEED)
|
||||
&& !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING))
|
||||
&& ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
|
||||
{
|
||||
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
|
||||
{
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
}
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
}
|
||||
else
|
||||
{
|
||||
isMoveAffectedByPartnerAbility = FALSE;
|
||||
}
|
||||
break;
|
||||
case ABILITY_CONTRARY:
|
||||
if (IsStatLoweringEffect(effect))
|
||||
case ABILITY_DEFIANT:
|
||||
case ABILITY_COMPETITIVE:
|
||||
if (IsStatLoweringEffect(effect) && isFriendlyFireOK && ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
|
||||
{
|
||||
RETURN_SCORE_PLUS(DECENT_EFFECT);
|
||||
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
|
||||
{
|
||||
ADJUST_SCORE(GOOD_EFFECT);
|
||||
}
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
}
|
||||
else
|
||||
{
|
||||
isMoveAffectedByPartnerAbility = FALSE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -3163,6 +3408,11 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
||||
// attacker move effects specifically targeting partner
|
||||
if (!partnerProtecting)
|
||||
{
|
||||
if (wouldPartnerFaint)
|
||||
{
|
||||
RETURN_SCORE_MINUS(30);
|
||||
}
|
||||
|
||||
switch (effect)
|
||||
{
|
||||
case EFFECT_SPICY_EXTRACT:
|
||||
@ -3200,11 +3450,16 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
||||
case EFFECT_BEAT_UP:
|
||||
if (atkPartnerAbility == ABILITY_JUSTIFIED
|
||||
&& moveType == TYPE_DARK
|
||||
&& !DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move)
|
||||
&& !IsBattleMoveStatus(move)
|
||||
&& HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL)
|
||||
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)
|
||||
&& !CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING))
|
||||
&& !wouldPartnerFaint)
|
||||
{
|
||||
if (isFriendlyFireOK)
|
||||
{
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
}
|
||||
RETURN_SCORE_PLUS(WEAK_EFFECT);
|
||||
}
|
||||
break;
|
||||
@ -3298,7 +3553,12 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
||||
} // attacker move effects
|
||||
} // check partner protecting
|
||||
|
||||
ADJUST_SCORE(-30); // otherwise, don't target partner
|
||||
if ((isMoveAffectedByPartnerAbility && (score <= AI_SCORE_DEFAULT)) || !isMoveAffectedByPartnerAbility)
|
||||
{
|
||||
RETURN_SCORE_MINUS(10);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else // checking opponent
|
||||
{
|
||||
@ -3356,12 +3616,13 @@ 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()
|
||||
&& noOfHitsToFaintPartner != 0 // Immunity check
|
||||
&& IsBattlerAlive(partnerBattler)
|
||||
&& GetBattlerMoveTargetType(battlerAtk, move) == MOVE_TARGET_FOES_AND_ALLY
|
||||
&& !(noOfHitsToFaintPartner < 4 && hitsToFaintOpposingBattler == 1)
|
||||
&& noOfHitsToFaintPartner < 7);
|
||||
&& !(noOfHitsToFaintPartner < friendlyFireThreshold && hitsToFaintOpposingBattler == 1)
|
||||
&& noOfHitsToFaintPartner < (friendlyFireThreshold * 2));
|
||||
}
|
||||
|
||||
static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId)
|
||||
|
||||
@ -1588,6 +1588,51 @@ bool32 IsHazardClearingMove(u32 move)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool32 IsAllyProtectingFromMove(u32 battlerAtk, u32 attackerMove, u32 allyMove)
|
||||
{
|
||||
enum BattleMoveEffects effect = GetMoveEffect(allyMove);
|
||||
|
||||
if (effect != EFFECT_PROTECT)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
enum ProtectMethod protectMethod = GetMoveProtectMethod(allyMove);
|
||||
|
||||
if (protectMethod == PROTECT_QUICK_GUARD)
|
||||
{
|
||||
u32 priority = GetBattleMovePriority(battlerAtk, gAiLogicData->abilities[battlerAtk], attackerMove);
|
||||
return (priority > 0);
|
||||
}
|
||||
|
||||
if (IsBattleMoveStatus(attackerMove))
|
||||
{
|
||||
switch (protectMethod)
|
||||
{
|
||||
case PROTECT_NORMAL:
|
||||
case PROTECT_CRAFTY_SHIELD:
|
||||
case PROTECT_MAX_GUARD:
|
||||
case PROTECT_WIDE_GUARD:
|
||||
return TRUE;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (protectMethod)
|
||||
{
|
||||
case PROTECT_CRAFTY_SHIELD:
|
||||
return FALSE;
|
||||
default:
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool32 IsMoveRedirectionPrevented(u32 battlerAtk, u32 move, u32 atkAbility)
|
||||
{
|
||||
if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_NEGATE_UNAWARE)
|
||||
@ -4566,6 +4611,78 @@ bool32 HasBattlerSideAbility(u32 battler, u32 ability, struct AiLogicData *aiDat
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
u32 GetFriendlyFireKOThreshold(u32 battler)
|
||||
{
|
||||
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_RISKY)
|
||||
return FRIENDLY_FIRE_RISKY_THRESHOLD;
|
||||
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_CONSERVATIVE)
|
||||
return FRIENDLY_FIRE_CONSERVATIVE_THRESHOLD;
|
||||
|
||||
return FRIENDLY_FIRE_NORMAL_THRESHOLD;
|
||||
}
|
||||
|
||||
bool32 IsMoxieTypeAbility(u32 ability)
|
||||
{
|
||||
switch (ability)
|
||||
{
|
||||
case ABILITY_MOXIE:
|
||||
case ABILITY_BEAST_BOOST:
|
||||
case ABILITY_CHILLING_NEIGH:
|
||||
case ABILITY_AS_ONE_ICE_RIDER:
|
||||
case ABILITY_GRIM_NEIGH:
|
||||
case ABILITY_AS_ONE_SHADOW_RIDER:
|
||||
return TRUE;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Should the AI use a spread move to deliberately activate its partner's ability?
|
||||
bool32 ShouldTriggerAbility(u32 battler, u32 ability)
|
||||
{
|
||||
switch (ability)
|
||||
{
|
||||
case ABILITY_LIGHTNING_ROD:
|
||||
case ABILITY_STORM_DRAIN:
|
||||
if (B_REDIRECT_ABILITY_IMMUNITY < GEN_5)
|
||||
return FALSE;
|
||||
else
|
||||
return (BattlerStatCanRise(battler, ability, STAT_SPATK) && HasMoveWithCategory(battler, DAMAGE_CATEGORY_SPECIAL));
|
||||
|
||||
case ABILITY_DEFIANT:
|
||||
case ABILITY_JUSTIFIED:
|
||||
case ABILITY_MOXIE:
|
||||
case ABILITY_SAP_SIPPER:
|
||||
case ABILITY_THERMAL_EXCHANGE:
|
||||
return (BattlerStatCanRise(battler, ability, STAT_ATK) && HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL));
|
||||
|
||||
case ABILITY_COMPETITIVE:
|
||||
return (BattlerStatCanRise(battler, ability, STAT_SPATK) && HasMoveWithCategory(battler, DAMAGE_CATEGORY_SPECIAL));
|
||||
|
||||
case ABILITY_CONTRARY:
|
||||
return TRUE;
|
||||
|
||||
case ABILITY_DRY_SKIN:
|
||||
case ABILITY_VOLT_ABSORB:
|
||||
case ABILITY_WATER_ABSORB:
|
||||
return (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_HP_AWARE);
|
||||
|
||||
case ABILITY_RATTLED:
|
||||
case ABILITY_STEAM_ENGINE:
|
||||
return BattlerStatCanRise(battler, ability, STAT_SPEED);
|
||||
|
||||
case ABILITY_FLASH_FIRE:
|
||||
return (HasMoveWithType(battler, TYPE_FIRE) && !gDisableStructs[battler].flashFireBoosted);
|
||||
|
||||
case ABILITY_WATER_COMPACTION:
|
||||
case ABILITY_WELL_BAKED_BODY:
|
||||
return (BattlerStatCanRise(battler, ability, STAT_DEF));
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
u32 GetThinkingBattler(u32 battler)
|
||||
{
|
||||
if (gAiLogicData->aiPredictionInProgress)
|
||||
|
||||
@ -369,88 +369,6 @@ AI_SINGLE_BATTLE_TEST("AI won't use ground type attacks against flying type Poke
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI won't use a Weather changing move if partner already chose such move")
|
||||
{
|
||||
u32 j, k;
|
||||
static const u16 weatherMoves[] = {MOVE_SUNNY_DAY, MOVE_HAIL, MOVE_RAIN_DANCE, MOVE_SANDSTORM, MOVE_SNOWSCAPE};
|
||||
u16 weatherMoveLeft = MOVE_NONE, weatherMoveRight = MOVE_NONE;
|
||||
|
||||
for (j = 0; j < ARRAY_COUNT(weatherMoves); j++)
|
||||
{
|
||||
for (k = 0; k < ARRAY_COUNT(weatherMoves); k++)
|
||||
{
|
||||
PARAMETRIZE { weatherMoveLeft = weatherMoves[j]; weatherMoveRight = weatherMoves[k]; }
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(weatherMoveLeft); }
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SCRATCH, weatherMoveRight); }
|
||||
} WHEN {
|
||||
TURN { NOT_EXPECT_MOVE(opponentRight, weatherMoveRight);
|
||||
SCORE_LT_VAL(opponentRight, weatherMoveRight, AI_SCORE_DEFAULT, target:playerLeft);
|
||||
SCORE_LT_VAL(opponentRight, weatherMoveRight, AI_SCORE_DEFAULT, target:playerRight);
|
||||
SCORE_LT_VAL(opponentRight, weatherMoveRight, AI_SCORE_DEFAULT, target:opponentLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will not use Helping Hand if partner does not have any damage moves")
|
||||
{
|
||||
u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE;
|
||||
|
||||
PARAMETRIZE { move1 = MOVE_LEER; move2 = MOVE_TOXIC; }
|
||||
PARAMETRIZE { move1 = MOVE_HELPING_HAND; move2 = MOVE_PROTECT; }
|
||||
PARAMETRIZE { move1 = MOVE_ACUPRESSURE; move2 = MOVE_DOUBLE_TEAM; move3 = MOVE_TOXIC; move4 = MOVE_PROTECT; }
|
||||
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HELPING_HAND, MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(move1, move2, move3, move4); }
|
||||
} WHEN {
|
||||
TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_HELPING_HAND);
|
||||
SCORE_LT_VAL(opponentLeft, MOVE_HELPING_HAND, AI_SCORE_DEFAULT, target:playerLeft);
|
||||
SCORE_LT_VAL(opponentLeft, MOVE_HELPING_HAND, AI_SCORE_DEFAULT, target:playerRight);
|
||||
SCORE_LT_VAL(opponentLeft, MOVE_HELPING_HAND, AI_SCORE_DEFAULT, target:opponentLeft);
|
||||
}
|
||||
} SCENE {
|
||||
NOT MESSAGE("The opposing Wobbuffet used Helping Hand!");
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will not use a status move if partner already chose Helping Hand")
|
||||
{
|
||||
s32 j;
|
||||
u32 statusMove = MOVE_NONE;
|
||||
|
||||
for (j = MOVE_NONE + 1; j < MOVES_COUNT; j++)
|
||||
{
|
||||
if (GetMoveCategory(j) == DAMAGE_CATEGORY_STATUS) {
|
||||
PARAMETRIZE { statusMove = j; }
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HELPING_HAND); }
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SCRATCH, statusMove); }
|
||||
} WHEN {
|
||||
TURN { NOT_EXPECT_MOVE(opponentRight, statusMove);
|
||||
SCORE_LT_VAL(opponentRight, statusMove, AI_SCORE_DEFAULT, target:playerLeft);
|
||||
SCORE_LT_VAL(opponentRight, statusMove, AI_SCORE_DEFAULT, target:playerRight);
|
||||
SCORE_LT_VAL(opponentRight, statusMove, AI_SCORE_DEFAULT, target:opponentLeft);
|
||||
}
|
||||
} SCENE {
|
||||
MESSAGE("The opposing Wobbuffet used Helping Hand!");
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI without any flags chooses moves at random - singles")
|
||||
{
|
||||
@ -641,79 +559,6 @@ AI_SINGLE_BATTLE_TEST("AI will choose Superpower over Outrage with Contrary")
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will not choose Earthquake if it damages the partner")
|
||||
{
|
||||
u32 species;
|
||||
|
||||
PARAMETRIZE { species = SPECIES_CHARIZARD; }
|
||||
PARAMETRIZE { species = SPECIES_CHARMANDER; }
|
||||
PARAMETRIZE { species = SPECIES_CHIKORITA; }
|
||||
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_PHANPY) { Moves(MOVE_EARTHQUAKE, MOVE_SCRATCH); }
|
||||
OPPONENT(species) { Moves(MOVE_CELEBRATE); }
|
||||
} WHEN {
|
||||
if (species == SPECIES_CHARIZARD)
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
|
||||
else
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will choose Earthquake if partner is not alive")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_EARTHQUAKE, MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_PIKACHU) { HP(1); Moves(MOVE_CELEBRATE); }
|
||||
} WHEN {
|
||||
TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentRight); }
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will choose Earthquake if it kill an opposing mon and does 1/3 of damage to AI")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_EARTHQUAKE, MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_PARAS) { Moves(MOVE_CELEBRATE); }
|
||||
} WHEN {
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will the see a corresponding absorbing ability on partner to one of its moves")
|
||||
{
|
||||
u32 ability;
|
||||
PARAMETRIZE { ability = ABILITY_LIGHTNING_ROD; }
|
||||
PARAMETRIZE { ability = ABILITY_STATIC; }
|
||||
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_DISCHARGE, MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_PIKACHU) { HP(1); Ability(ability); Moves(MOVE_CELEBRATE); }
|
||||
} WHEN {
|
||||
if (ability == ABILITY_LIGHTNING_ROD)
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_DISCHARGE); }
|
||||
else
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_SCRATCH); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI calculates guaranteed criticals and detects critical immunity")
|
||||
{
|
||||
u32 ability;
|
||||
@ -737,22 +582,6 @@ AI_SINGLE_BATTLE_TEST("AI calculates guaranteed criticals and detects critical i
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI recognizes Volt Absorb received from Trace")
|
||||
{
|
||||
KNOWN_FAILING; // MGriffin's PR that switched two turn charging moves in AI tests broke this test, waiting on a fix
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_MAGNETON);
|
||||
PLAYER(SPECIES_GARDEVOIR) { Ability(ABILITY_TRACE); }
|
||||
OPPONENT(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_THUNDER_WAVE, MOVE_THUNDERSHOCK, MOVE_WATER_GUN); }
|
||||
OPPONENT(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_THUNDER_WAVE, MOVE_THUNDERSHOCK, MOVE_WATER_GUN); }
|
||||
} WHEN {
|
||||
TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDERSHOCK); NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDER_WAVE); NOT_EXPECT_MOVE(opponentRight, MOVE_THUNDER_WAVE); }
|
||||
} THEN {
|
||||
EXPECT(gAiLogicData->abilities[B_POSITION_PLAYER_RIGHT] == ABILITY_VOLT_ABSORB);
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI avoids contact moves against rocky helmet")
|
||||
{
|
||||
u32 item;
|
||||
|
||||
@ -241,21 +241,6 @@ AI_SINGLE_BATTLE_TEST("AI chooses moves that cure inactive party members")
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI prioritizes Skill Swapping Contrary to allied mons that would benefit from it")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP);
|
||||
ASSUME(GetMoveAdditionalEffectById(MOVE_OVERHEAT, 0)->moveEffect == MOVE_EFFECT_SP_ATK_MINUS_2);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE);
|
||||
PLAYER(SPECIES_WOBBUFFET) { Speed(3); }
|
||||
PLAYER(SPECIES_WOBBUFFET) { Speed(3); }
|
||||
OPPONENT(SPECIES_SPINDA) { Ability(ABILITY_CONTRARY); Speed(5); Moves(MOVE_SKILL_SWAP, MOVE_ENCORE, MOVE_FAKE_TEARS, MOVE_SWAGGER); }
|
||||
OPPONENT(SPECIES_ARCANINE) { Ability(ABILITY_INTIMIDATE); Speed(4); Moves (MOVE_OVERHEAT); }
|
||||
} WHEN {
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_SKILL_SWAP, target:opponentRight); EXPECT_MOVE(opponentRight, MOVE_OVERHEAT); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI prioritizes Pursuit if it would KO opponent")
|
||||
{
|
||||
GIVEN {
|
||||
|
||||
412
test/battle/ai/ai_doubles.c
Normal file
412
test/battle/ai/ai_doubles.c
Normal file
@ -0,0 +1,412 @@
|
||||
#include "global.h"
|
||||
#include "test/battle.h"
|
||||
#include "battle_ai_util.h"
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI won't use a Weather changing move if partner already chose such move")
|
||||
{
|
||||
u32 j, k;
|
||||
static const u16 weatherMoves[] = {MOVE_SUNNY_DAY, MOVE_HAIL, MOVE_RAIN_DANCE, MOVE_SANDSTORM, MOVE_SNOWSCAPE};
|
||||
u16 weatherMoveLeft = MOVE_NONE, weatherMoveRight = MOVE_NONE;
|
||||
|
||||
for (j = 0; j < ARRAY_COUNT(weatherMoves); j++)
|
||||
{
|
||||
for (k = 0; k < ARRAY_COUNT(weatherMoves); k++)
|
||||
{
|
||||
PARAMETRIZE { weatherMoveLeft = weatherMoves[j]; weatherMoveRight = weatherMoves[k]; }
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(weatherMoveLeft); }
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SCRATCH, weatherMoveRight); }
|
||||
} WHEN {
|
||||
TURN { NOT_EXPECT_MOVE(opponentRight, weatherMoveRight);
|
||||
SCORE_LT_VAL(opponentRight, weatherMoveRight, AI_SCORE_DEFAULT, target:playerLeft);
|
||||
SCORE_LT_VAL(opponentRight, weatherMoveRight, AI_SCORE_DEFAULT, target:playerRight);
|
||||
SCORE_LT_VAL(opponentRight, weatherMoveRight, AI_SCORE_DEFAULT, target:opponentLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will not use Helping Hand if partner does not have any damage moves")
|
||||
{
|
||||
u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE;
|
||||
|
||||
PARAMETRIZE { move1 = MOVE_LEER; move2 = MOVE_TOXIC; }
|
||||
PARAMETRIZE { move1 = MOVE_HELPING_HAND; move2 = MOVE_PROTECT; }
|
||||
PARAMETRIZE { move1 = MOVE_ACUPRESSURE; move2 = MOVE_DOUBLE_TEAM; move3 = MOVE_TOXIC; move4 = MOVE_PROTECT; }
|
||||
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HELPING_HAND, MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(move1, move2, move3, move4); }
|
||||
} WHEN {
|
||||
TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_HELPING_HAND);
|
||||
SCORE_LT_VAL(opponentLeft, MOVE_HELPING_HAND, AI_SCORE_DEFAULT, target:playerLeft);
|
||||
SCORE_LT_VAL(opponentLeft, MOVE_HELPING_HAND, AI_SCORE_DEFAULT, target:playerRight);
|
||||
SCORE_LT_VAL(opponentLeft, MOVE_HELPING_HAND, AI_SCORE_DEFAULT, target:opponentLeft);
|
||||
}
|
||||
} SCENE {
|
||||
NOT MESSAGE("The opposing Wobbuffet used Helping Hand!");
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will not use a status move if partner already chose Helping Hand")
|
||||
{
|
||||
s32 j;
|
||||
u32 statusMove = MOVE_NONE;
|
||||
|
||||
for (j = MOVE_NONE + 1; j < MOVES_COUNT; j++)
|
||||
{
|
||||
if (GetMoveCategory(j) == DAMAGE_CATEGORY_STATUS) {
|
||||
PARAMETRIZE { statusMove = j; }
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HELPING_HAND); }
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SCRATCH, statusMove); }
|
||||
} WHEN {
|
||||
TURN { NOT_EXPECT_MOVE(opponentRight, statusMove);
|
||||
SCORE_LT_VAL(opponentRight, statusMove, AI_SCORE_DEFAULT, target:playerLeft);
|
||||
SCORE_LT_VAL(opponentRight, statusMove, AI_SCORE_DEFAULT, target:playerRight);
|
||||
SCORE_LT_VAL(opponentRight, statusMove, AI_SCORE_DEFAULT, target:opponentLeft);
|
||||
}
|
||||
} SCENE {
|
||||
MESSAGE("The opposing Wobbuffet used Helping Hand!");
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will not choose Earthquake if it damages the partner without a positive effect")
|
||||
{
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
|
||||
u32 species;
|
||||
|
||||
PARAMETRIZE { species = SPECIES_CHARIZARD; }
|
||||
PARAMETRIZE { species = SPECIES_CHARMANDER; }
|
||||
PARAMETRIZE { species = SPECIES_CHIKORITA; }
|
||||
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_PHANPY) { Moves(MOVE_EARTHQUAKE, MOVE_SCRATCH); }
|
||||
OPPONENT(species) { Moves(MOVE_CELEBRATE); }
|
||||
} WHEN {
|
||||
if (species == SPECIES_CHARIZARD)
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
|
||||
else
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI recognizes its ally's Telepathy")
|
||||
{
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_PHANPY) { Moves(MOVE_EARTHQUAKE, MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_ELGYEM) { Level(1); Ability(ABILITY_TELEPATHY); }
|
||||
} WHEN {
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will choose Bulldoze if it triggers its ally's ability but will not KO the ally needlessly")
|
||||
{
|
||||
ASSUME(GetMoveTarget(MOVE_BULLDOZE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
ASSUME(GetMoveType(MOVE_BULLDOZE) == TYPE_GROUND);
|
||||
ASSUME(MoveHasAdditionalEffect(MOVE_BULLDOZE, MOVE_EFFECT_SPD_MINUS_1));
|
||||
|
||||
u32 species, ability, currentHP;
|
||||
|
||||
PARAMETRIZE { species = SPECIES_KINGAMBIT; ability = ABILITY_DEFIANT; currentHP = 400; }
|
||||
PARAMETRIZE { species = SPECIES_SHUCKLE; ability = ABILITY_CONTRARY; currentHP = 400; }
|
||||
PARAMETRIZE { species = SPECIES_PAWNIARD; ability = ABILITY_PRESSURE; currentHP = 1; }
|
||||
PARAMETRIZE { species = SPECIES_PAWNIARD; ability = ABILITY_DEFIANT; currentHP = 1; }
|
||||
PARAMETRIZE { species = SPECIES_SHUCKLE; ability = ABILITY_CONTRARY; currentHP = 1; }
|
||||
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_PHANPY) { Moves(MOVE_BULLDOZE, MOVE_HIGH_HORSEPOWER); }
|
||||
OPPONENT(species) { Moves(MOVE_CELEBRATE, MOVE_POUND); HP(currentHP); Ability(ability); }
|
||||
} WHEN {
|
||||
if (currentHP != 1)
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_BULLDOZE); }
|
||||
else
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_HIGH_HORSEPOWER); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will choose Beat Up on an ally with Justified if it will benefit the ally")
|
||||
{
|
||||
ASSUME(GetMoveEffect(MOVE_BEAT_UP) == EFFECT_BEAT_UP);
|
||||
ASSUME(GetMoveType(MOVE_BEAT_UP) == TYPE_DARK);
|
||||
|
||||
u32 defAbility, atkAbility, currentHP;
|
||||
|
||||
PARAMETRIZE { defAbility = ABILITY_FLASH_FIRE; atkAbility = ABILITY_SCRAPPY; currentHP = 400; }
|
||||
PARAMETRIZE { defAbility = ABILITY_JUSTIFIED; atkAbility = ABILITY_SCRAPPY; currentHP = 400; }
|
||||
PARAMETRIZE { defAbility = ABILITY_JUSTIFIED; atkAbility = ABILITY_MOLD_BREAKER; currentHP = 400; }
|
||||
PARAMETRIZE { defAbility = ABILITY_JUSTIFIED; atkAbility = ABILITY_SCRAPPY; currentHP = 1; }
|
||||
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_CLEFABLE);
|
||||
OPPONENT(SPECIES_PANGORO) { Ability(atkAbility); Moves(MOVE_BEAT_UP); }
|
||||
OPPONENT(SPECIES_GROWLITHE) { Moves(MOVE_CELEBRATE, MOVE_TACKLE); HP(currentHP); Ability(defAbility); }
|
||||
} WHEN {
|
||||
if (!(currentHP == 1) && (defAbility == ABILITY_JUSTIFIED) && (atkAbility != ABILITY_MOLD_BREAKER))
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_BEAT_UP, target: opponentRight); }
|
||||
else
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_BEAT_UP, target: playerLeft); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will choose Earthquake if partner is not alive")
|
||||
{
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND);
|
||||
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_EARTHQUAKE, MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_PIKACHU) { HP(1); Moves(MOVE_CELEBRATE); }
|
||||
} WHEN {
|
||||
TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentRight); }
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will choose Earthquake if it kills one opposing mon and does not kill the partner needlessly")
|
||||
{
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND);
|
||||
|
||||
u32 currentHP;
|
||||
PARAMETRIZE { currentHP = 1; }
|
||||
PARAMETRIZE { currentHP = 200; }
|
||||
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_EARTHQUAKE, MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_PARAS) { Moves(MOVE_CELEBRATE); HP(currentHP); }
|
||||
} WHEN {
|
||||
if (currentHP == 1)
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_SCRATCH); }
|
||||
else
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will choose Earthquake if it kills one opposing mon and a partner it believes is about to die")
|
||||
{
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND);
|
||||
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
|
||||
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_SCRATCH); }
|
||||
PLAYER(SPECIES_WOBBUFFET) { HP(1); Moves(MOVE_CELEBRATE, MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_EARTHQUAKE, MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_PARAS) { Moves(MOVE_CELEBRATE); HP(1); }
|
||||
} WHEN {
|
||||
TURN { MOVE(playerLeft, MOVE_SCRATCH); }
|
||||
TURN { MOVE(playerRight, MOVE_SCRATCH); }
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will choose Earthquake if it kills both opposing mons")
|
||||
{
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND);
|
||||
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
|
||||
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_EARTHQUAKE, MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_PARAS) { Moves(MOVE_CELEBRATE); HP(1); }
|
||||
} WHEN {
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will trigger its ally's Weakness Policy")
|
||||
{
|
||||
ASSUME(gItemsInfo[ITEM_WEAKNESS_POLICY].holdEffect == HOLD_EFFECT_WEAKNESS_POLICY);
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND);
|
||||
|
||||
u32 species;
|
||||
PARAMETRIZE { species = SPECIES_INCINEROAR; }
|
||||
PARAMETRIZE { species = SPECIES_CLEFFA; }
|
||||
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_EARTHQUAKE, MOVE_STOMPING_TANTRUM); }
|
||||
OPPONENT(species) { Moves(MOVE_CELEBRATE); Item(ITEM_WEAKNESS_POLICY); }
|
||||
} WHEN {
|
||||
if (species == SPECIES_INCINEROAR)
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
|
||||
else
|
||||
TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will only explode and kill everything on the field with Risky or Will Suicide")
|
||||
{
|
||||
ASSUME(GetMoveTarget(MOVE_EXPLOSION) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION);
|
||||
|
||||
u32 aiFlags;
|
||||
|
||||
PARAMETRIZE { aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT; }
|
||||
PARAMETRIZE { aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_RISKY; }
|
||||
PARAMETRIZE { aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_WILL_SUICIDE; }
|
||||
|
||||
GIVEN {
|
||||
AI_FLAGS(aiFlags);
|
||||
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
|
||||
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
|
||||
OPPONENT(SPECIES_ELECTRODE) { Moves(MOVE_EXPLOSION, MOVE_ELECTRO_BALL); }
|
||||
OPPONENT(SPECIES_ELECTRODE) { Moves(MOVE_CELEBRATE); HP(1); }
|
||||
} WHEN {
|
||||
if (aiFlags == (AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT))
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_ELECTRO_BALL); }
|
||||
else
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_EXPLOSION); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI sees corresponding absorbing abilities on partners")
|
||||
{
|
||||
ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC);
|
||||
ASSUME(GetMoveTarget(MOVE_LAVA_PLUME) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
ASSUME(GetMoveType(MOVE_LAVA_PLUME) == TYPE_FIRE);
|
||||
ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER);
|
||||
ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND);
|
||||
|
||||
u32 ability, move, species;
|
||||
|
||||
PARAMETRIZE { species = SPECIES_PSYDUCK; ability = ABILITY_CLOUD_NINE; move = MOVE_DISCHARGE; }
|
||||
PARAMETRIZE { species = SPECIES_PIKACHU; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; }
|
||||
PARAMETRIZE { species = SPECIES_LANTURN; ability = ABILITY_VOLT_ABSORB; move = MOVE_DISCHARGE; }
|
||||
PARAMETRIZE { species = SPECIES_EMOLGA; ability = ABILITY_MOTOR_DRIVE; move = MOVE_DISCHARGE; }
|
||||
PARAMETRIZE { species = SPECIES_SEAKING; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; }
|
||||
PARAMETRIZE { species = SPECIES_GROWLITHE; ability = ABILITY_FLASH_FIRE; move = MOVE_LAVA_PLUME; }
|
||||
PARAMETRIZE { species = SPECIES_DACHSBUN; ability = ABILITY_WELL_BAKED_BODY; move = MOVE_LAVA_PLUME; }
|
||||
PARAMETRIZE { species = SPECIES_QUAGSIRE; ability = ABILITY_WATER_ABSORB; move = MOVE_SURF; }
|
||||
PARAMETRIZE { species = SPECIES_SHELLOS; ability = ABILITY_STORM_DRAIN; move = MOVE_SURF; }
|
||||
PARAMETRIZE { species = SPECIES_UNOWN; ability = ABILITY_LEVITATE; move = MOVE_EARTHQUAKE; }
|
||||
PARAMETRIZE { species = SPECIES_ORTHWORM; ability = ABILITY_EARTH_EATER; move = MOVE_EARTHQUAKE; }
|
||||
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE);
|
||||
PLAYER(SPECIES_ZIGZAGOON);
|
||||
PLAYER(SPECIES_ZIGZAGOON);
|
||||
OPPONENT(SPECIES_SLAKING) { Moves(move, MOVE_SCRATCH); }
|
||||
OPPONENT(species) { HP(1); Ability(ability); Moves(MOVE_POUND, MOVE_EMBER, MOVE_ROUND); }
|
||||
} WHEN {
|
||||
if (ability != ABILITY_CLOUD_NINE)
|
||||
TURN { EXPECT_MOVE(opponentLeft, move); }
|
||||
else
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_SCRATCH); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI knows if redirection abilities provide immunity to allies")
|
||||
{
|
||||
KNOWN_FAILING;
|
||||
ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC);
|
||||
ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER);
|
||||
|
||||
u32 ability, move, species, config;
|
||||
|
||||
PARAMETRIZE { species = SPECIES_SEAKING; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; config = GEN_4; }
|
||||
PARAMETRIZE { species = SPECIES_SEAKING; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; config = GEN_5; }
|
||||
PARAMETRIZE { species = SPECIES_SHELLOS; ability = ABILITY_STORM_DRAIN; move = MOVE_SURF; config = GEN_4; }
|
||||
PARAMETRIZE { species = SPECIES_SHELLOS; ability = ABILITY_STORM_DRAIN; move = MOVE_SURF; config = GEN_5; }
|
||||
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE);
|
||||
WITH_CONFIG(B_REDIRECT_ABILITY_IMMUNITY, config);
|
||||
PLAYER(SPECIES_ZIGZAGOON);
|
||||
PLAYER(SPECIES_ZIGZAGOON);
|
||||
OPPONENT(SPECIES_SLAKING) { Moves(move, MOVE_SCRATCH); }
|
||||
OPPONENT(species) { HP(1); Ability(ability); Moves(MOVE_ROUND); }
|
||||
} WHEN {
|
||||
if (config == GEN_5)
|
||||
TURN { EXPECT_MOVE(opponentLeft, move); }
|
||||
else
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_SCRATCH); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI recognizes Volt Absorb received from Trace")
|
||||
{
|
||||
KNOWN_FAILING; // MGriffin's PR that switched two turn charging moves in AI tests broke this test, waiting on a fix
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_MAGNETON);
|
||||
PLAYER(SPECIES_GARDEVOIR) { Ability(ABILITY_TRACE); }
|
||||
OPPONENT(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_THUNDER_WAVE, MOVE_THUNDERSHOCK, MOVE_WATER_GUN); }
|
||||
OPPONENT(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_THUNDER_WAVE, MOVE_THUNDERSHOCK, MOVE_WATER_GUN); }
|
||||
} WHEN {
|
||||
TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDERSHOCK); NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDER_WAVE); NOT_EXPECT_MOVE(opponentRight, MOVE_THUNDER_WAVE); }
|
||||
} THEN {
|
||||
EXPECT(gAiLogicData->abilities[B_POSITION_PLAYER_RIGHT] == ABILITY_VOLT_ABSORB);
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI prioritizes Skill Swapping Contrary to allied mons that would benefit from it")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP);
|
||||
ASSUME(GetMoveAdditionalEffectById(MOVE_OVERHEAT, 0)->moveEffect == MOVE_EFFECT_SP_ATK_MINUS_2);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE);
|
||||
PLAYER(SPECIES_WOBBUFFET) { Speed(3); }
|
||||
PLAYER(SPECIES_WOBBUFFET) { Speed(3); }
|
||||
OPPONENT(SPECIES_SPINDA) { Ability(ABILITY_CONTRARY); Speed(5); Moves(MOVE_SKILL_SWAP, MOVE_ENCORE, MOVE_FAKE_TEARS, MOVE_SWAGGER); }
|
||||
OPPONENT(SPECIES_ARCANINE) { Ability(ABILITY_INTIMIDATE); Speed(4); Moves (MOVE_OVERHEAT); }
|
||||
} WHEN {
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_SKILL_SWAP, target:opponentRight); EXPECT_MOVE(opponentRight, MOVE_OVERHEAT); }
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user