Move most damage AI_BadMove checks to AI_CalcDamage (#4238)

* Move a couple damage AI_BadMove checks to AI_CalcDamage

* re-add effectivness score decrease

* reduce score for bad move in ai_checkviability

* review changes
This commit is contained in:
Alex 2024-03-04 09:54:04 +01:00 committed by GitHub
parent 5acc770f00
commit 8d58af4d33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 131 additions and 103 deletions

View file

@ -798,47 +798,30 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
SetTypeBeforeUsingMove(move, battlerAtk);
GET_MOVE_TYPE(move, moveType);
if (gMovesInfo[move].powderMove && !IsAffectedByPowder(battlerDef, aiData->abilities[battlerDef], aiData->holdEffects[battlerDef]))
RETURN_SCORE_MINUS(10);
if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
RETURN_SCORE_MINUS(10);
if (IsTwoTurnNotSemiInvulnerableMove(battlerAtk, move) && CanTargetFaintAi(battlerDef, battlerAtk))
RETURN_SCORE_MINUS(10);
// check if negates type
switch (effectiveness)
{
case AI_EFFECTIVENESS_x0:
RETURN_SCORE_MINUS(20);
break;
case AI_EFFECTIVENESS_x0_125:
case AI_EFFECTIVENESS_x0_25:
RETURN_SCORE_MINUS(10);
break;
}
// check non-user target
if (!(moveTarget & MOVE_TARGET_USER))
{
// handle negative checks on non-user target
// check powder moves
if (gMovesInfo[move].powderMove && !IsAffectedByPowder(battlerDef, aiData->abilities[battlerDef], aiData->holdEffects[battlerDef]))
{
RETURN_SCORE_MINUS(20);
}
// check ground immunities
if (moveType == TYPE_GROUND
&& !IsBattlerGrounded(battlerDef)
&& ((aiData->abilities[battlerDef] == ABILITY_LEVITATE
&& DoesBattlerIgnoreAbilityChecks(aiData->abilities[battlerAtk], move))
|| aiData->holdEffects[battlerDef] == HOLD_EFFECT_AIR_BALLOON
|| (gStatuses3[battlerDef] & (STATUS3_MAGNET_RISE | STATUS3_TELEKINESIS)))
&& move != MOVE_THOUSAND_ARROWS)
{
RETURN_SCORE_MINUS(20);
}
// check off screen
if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
RETURN_SCORE_MINUS(20); // if target off screen and we go first, don't use move
if (IsTwoTurnNotSemiInvulnerableMove(battlerAtk, move) && CanTargetFaintAi(battlerDef, battlerAtk))
RETURN_SCORE_MINUS(10);
// check if negates type
switch (effectiveness)
{
case AI_EFFECTIVENESS_x0:
RETURN_SCORE_MINUS(20);
break;
case AI_EFFECTIVENESS_x0_125:
case AI_EFFECTIVENESS_x0_25:
RETURN_SCORE_MINUS(10);
break;
}
// target ability checks
if (!DoesBattlerIgnoreAbilityChecks(aiData->abilities[battlerAtk], move))
{
@ -859,30 +842,10 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break;
}
break;
case ABILITY_VOLT_ABSORB:
case ABILITY_MOTOR_DRIVE:
case ABILITY_LIGHTNING_ROD:
if (moveType == TYPE_ELECTRIC)
RETURN_SCORE_MINUS(20);
break;
case ABILITY_WATER_ABSORB:
case ABILITY_DRY_SKIN:
case ABILITY_STORM_DRAIN:
if (moveType == TYPE_WATER)
RETURN_SCORE_MINUS(20);
break;
case ABILITY_FLASH_FIRE:
if (moveType == TYPE_FIRE)
RETURN_SCORE_MINUS(20);
break;
case ABILITY_WONDER_GUARD:
if (effectiveness < AI_EFFECTIVENESS_x2)
return 0;
break;
case ABILITY_SAP_SIPPER:
if (moveType == TYPE_GRASS)
RETURN_SCORE_MINUS(20);
break;
case ABILITY_JUSTIFIED:
if (moveType == TYPE_DARK && !IS_MOVE_STATUS(move))
RETURN_SCORE_MINUS(10);
@ -892,14 +855,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
&& (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_SOUNDPROOF:
if (gMovesInfo[move].soundMove)
RETURN_SCORE_MINUS(10);
break;
case ABILITY_BULLETPROOF:
if (gMovesInfo[move].ballisticMove)
RETURN_SCORE_MINUS(10);
break;
case ABILITY_DAZZLING:
case ABILITY_QUEENLY_MAJESTY:
case ABILITY_ARMOR_TAIL:
@ -1105,12 +1060,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-1);
}
break;
case EFFECT_DREAM_EATER:
if (!AI_IsBattlerAsleepOrComatose(battlerDef))
ADJUST_SCORE(-8);
else if (effectiveness == AI_EFFECTIVENESS_x0)
ADJUST_SCORE(-10);
break;
// stat raising effects
case EFFECT_ATTACK_UP:
case EFFECT_ATTACK_UP_2:
@ -1906,10 +1855,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
else if (aiData->hpPercents[battlerAtk] >= 90)
ADJUST_SCORE(-8); //No point in healing, but should at least do it if nothing better
break;
case EFFECT_SUPER_FANG:
if (aiData->hpPercents[battlerDef] < 50)
ADJUST_SCORE(-4);
break;
case EFFECT_RECOIL_IF_MISS:
if (aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD && AI_DATA->moveAccuracy[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] < 75)
ADJUST_SCORE(-6);
@ -1935,11 +1880,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break;
case EFFECT_METRONOME:
break;
case EFFECT_ENDEAVOR:
case EFFECT_PAIN_SPLIT:
if (gBattleMons[battlerAtk].hp > (gBattleMons[battlerAtk].hp + gBattleMons[battlerDef].hp) / 2)
ADJUST_SCORE(-10);
break;
case EFFECT_CONVERSION_2:
//TODO
@ -1965,9 +1905,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (gBattleMons[battlerDef].status2 & STATUS2_DESTINY_BOND)
ADJUST_SCORE(-10);
break;
case EFFECT_FALSE_SWIPE:
// TODO
break;
case EFFECT_HEAL_BELL:
if (!AnyPartyMemberStatused(battlerAtk, gMovesInfo[move].soundMove) || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))
ADJUST_SCORE(-10);
@ -2050,10 +1987,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|| DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
ADJUST_SCORE(-9);
break;
case EFFECT_FAIL_IF_NOT_ARG_TYPE:
if (!IS_BATTLER_OF_TYPE(battlerAtk, gMovesInfo[move].argument))
ADJUST_SCORE(-10);
break;
case EFFECT_DEFOG:
if (gSideStatuses[GetBattlerSide(battlerDef)]
& (SIDE_STATUS_REFLECT | SIDE_STATUS_LIGHTSCREEN | SIDE_STATUS_AURORA_VEIL | SIDE_STATUS_SAFEGUARD | SIDE_STATUS_MIST)
@ -2155,10 +2088,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (!HasMagicCoatAffectedMove(battlerDef))
ADJUST_SCORE(-10);
break;
case EFFECT_BELCH:
if (ItemId_GetPocket(GetUsedHeldItem(battlerAtk)) != POCKET_BERRIES)
ADJUST_SCORE(-10); // attacker has not consumed a berry
break;
case EFFECT_YAWN:
if (gStatuses3[battlerDef] & STATUS3_YAWN)
ADJUST_SCORE(-10);
@ -2578,10 +2507,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (!CanCamouflage(battlerAtk))
ADJUST_SCORE(-10);
break;
case EFFECT_LAST_RESORT:
if (!CanUseLastResort(battlerAtk))
ADJUST_SCORE(-10);
break;
case EFFECT_SYNCHRONOISE:
//Check holding ring target or is of same type
if (aiData->holdEffects[battlerDef] == HOLD_EFFECT_RING_TARGET
@ -2655,10 +2580,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
&& !BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF))
ADJUST_SCORE(-10);
break;
case EFFECT_LOW_KICK:
if (IsDynamaxed(battlerDef))
ADJUST_SCORE(-10);
break;
case EFFECT_UPPER_HAND:
if (predictedMove == MOVE_NONE || IS_MOVE_STATUS(predictedMove) || AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER || GetMovePriority(battlerDef, move) < 1 || GetMovePriority(battlerDef, move) > 3) // Opponent going first or not using priority move
ADJUST_SCORE(-10);
@ -3140,7 +3061,7 @@ static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId)
if (moves[i] != MOVE_NONE && gMovesInfo[moves[i]].power)
{
noOfHits[i] = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, i);
if (noOfHits[i] < leastHits)
if (noOfHits[i] < leastHits && noOfHits[i] != 0)
{
leastHits = noOfHits[i];
}
@ -4694,7 +4615,12 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
return score;
if (gMovesInfo[move].power)
score += AI_CompareDamagingMoves(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex);
{
if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == 0)
ADJUST_SCORE(-20);
else
score += AI_CompareDamagingMoves(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex);
}
// Calculates score based on effects of a move
score += AI_CalcMoveScore(battlerAtk, battlerDef, move);

View file

@ -366,10 +366,84 @@ static inline s32 LowestRollDmg(s32 dmg)
return dmg;
}
bool32 IsDamageMoveUsable(u32 move, u32 battlerAtk, u32 battlerDef)
{
s32 moveType;
struct AiLogicData *aiData = AI_DATA;
u32 battlerDefAbility;
if (DoesBattlerIgnoreAbilityChecks(aiData->abilities[battlerAtk], move))
battlerDefAbility = ABILITY_NONE;
else
battlerDefAbility = aiData->abilities[battlerDef];
SetTypeBeforeUsingMove(move, battlerAtk);
GET_MOVE_TYPE(move, moveType);
switch (battlerDefAbility)
{
case ABILITY_VOLT_ABSORB:
case ABILITY_MOTOR_DRIVE:
case ABILITY_LIGHTNING_ROD:
if (moveType == TYPE_ELECTRIC)
return TRUE;
break;
case ABILITY_WATER_ABSORB:
case ABILITY_DRY_SKIN:
case ABILITY_STORM_DRAIN:
if (moveType == TYPE_WATER)
return TRUE;
break;
case ABILITY_FLASH_FIRE:
if (moveType == TYPE_FIRE)
return TRUE;
break;
case ABILITY_SOUNDPROOF:
if (gMovesInfo[move].soundMove)
return TRUE;
break;
case ABILITY_BULLETPROOF:
if (gMovesInfo[move].ballisticMove)
return TRUE;
break;
case ABILITY_SAP_SIPPER:
if (moveType == TYPE_GRASS)
return TRUE;
break;
}
switch (gMovesInfo[move].effect)
{
case EFFECT_DREAM_EATER:
if (!AI_IsBattlerAsleepOrComatose(battlerDef))
return TRUE;
break;
case EFFECT_BELCH:
if (ItemId_GetPocket(GetUsedHeldItem(battlerAtk)) != POCKET_BERRIES)
return TRUE;
break;
case EFFECT_LAST_RESORT:
if (!CanUseLastResort(battlerAtk))
return TRUE;
break;
case EFFECT_LOW_KICK:
if (IsDynamaxed(battlerDef))
return TRUE;
break;
case EFFECT_FAIL_IF_NOT_ARG_TYPE:
if (!IS_BATTLER_OF_TYPE(battlerAtk, gMovesInfo[move].argument))
return TRUE;
break;
}
return FALSE;
}
s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather)
{
s32 dmg, moveType;
uq4_12_t effectivenessMultiplier;
bool32 isDamageMoveUnusable = FALSE;
struct AiLogicData *aiData = AI_DATA;
SetBattlerData(battlerAtk);
@ -393,8 +467,11 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes
SetTypeBeforeUsingMove(move, battlerAtk);
GET_MOVE_TYPE(move, moveType);
effectivenessMultiplier = CalcTypeEffectivenessMultiplier(move, moveType, battlerAtk, battlerDef, aiData->abilities[battlerDef], FALSE);
if (gMovesInfo[move].power)
isDamageMoveUnusable = IsDamageMoveUsable(move, battlerAtk, battlerDef);
effectivenessMultiplier = CalcTypeEffectivenessMultiplier(move, moveType, battlerAtk, battlerDef, aiData->abilities[battlerDef], FALSE);
if (gMovesInfo[move].power && !isDamageMoveUnusable)
{
s32 critChanceIndex, normalDmg, fixedBasePower, n;

View file

@ -752,3 +752,28 @@ AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spot
}
}
}
AI_SINGLE_BATTLE_TEST("AI will not choose Burn Up if the user lost the Fire typing")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_BURN_UP].effect == EFFECT_FAIL_IF_NOT_ARG_TYPE);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_CYNDAQUIL) { Moves(MOVE_BURN_UP, MOVE_EXTRASENSORY, MOVE_FLAMETHROWER); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_BURN_UP); }
TURN { EXPECT_MOVE(opponent, MOVE_FLAMETHROWER); }
}
}
AI_SINGLE_BATTLE_TEST("AI will choose Surf over Thunderbolt and Ice Beam if the opposing mon has Volt Absorb")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_THUNDERBOLT].type == TYPE_ELECTRIC);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_LANTURN) { Ability(ABILITY_VOLT_ABSORB); };
OPPONENT(SPECIES_LANTURN) { Moves(MOVE_THUNDERBOLT, MOVE_ICE_BEAM, MOVE_SURF); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_SURF); }
}
}