Improved AI for status curing; trainer items, Purify, Smelling Salts, Sparkling Aria (#7853)

This commit is contained in:
surskitty 2025-10-06 17:52:27 -04:00 committed by GitHub
parent 768c403850
commit f0444f0d3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 202 additions and 17 deletions

View File

@ -132,6 +132,8 @@ bool32 ShouldUseRecoilMove(u32 battlerAtk, u32 battlerDef, u32 recoilDmg, u32 mo
bool32 ShouldAbsorb(u32 battlerAtk, u32 battlerDef, u32 move, s32 damage);
bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent);
bool32 ShouldSetScreen(u32 battlerAtk, u32 battlerDef, enum BattleMoveEffects moveEffect);
bool32 ShouldCureStatus(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData);
bool32 ShouldCureStatusWithItem(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData);
enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 move, u32 moveIndex);
bool32 IsRecycleEncouragedItem(u32 item);
bool32 ShouldRestoreHpBerry(u32 battlerAtk, u32 item);

View File

@ -2194,10 +2194,13 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10);
else if (battlerDef == BATTLE_PARTNER(battlerAtk))
break; //Always heal your ally
else if (AI_BattlerAtMaxHp(battlerAtk))
ADJUST_SCORE(-10);
else if (aiData->hpPercents[battlerAtk] >= 90)
ADJUST_SCORE(-8); //No point in healing, but should at least do it if nothing better
else if (!ShouldCureStatus(battlerAtk, battlerDef, aiData))
{
if (AI_BattlerAtMaxHp(battlerAtk))
ADJUST_SCORE(-10);
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_RECOIL_IF_MISS:
if (aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD && gAiLogicData->moveAccuracy[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex] < 75
@ -3602,8 +3605,18 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (gBattleMons[battlerAtkPartner].status1 & STATUS1_ANY)
{
if (gBattleMons[battlerAtkPartner].status1 & STATUS1_CAN_MOVE)
{
if (ShouldCureStatus(battlerAtk, battlerAtkPartner, aiData))
ADJUST_SCORE(DECENT_EFFECT);
}
else
{
ADJUST_SCORE(DECENT_EFFECT);
}
if ((!IsBattlerAlive(LEFT_FOE(battlerAtk)) || ShouldRecover(battlerAtk, LEFT_FOE(battlerAtk), move, 50))
&& (!IsBattlerAlive(RIGHT_FOE(battlerAtk)) || ShouldRecover(battlerAtk, RIGHT_FOE(battlerAtk), move, 50)))
RETURN_SCORE_PLUS(WEAK_EFFECT);
RETURN_SCORE_PLUS(GOOD_EFFECT);
}
break;
case EFFECT_SWAGGER:
@ -4557,6 +4570,15 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru
if (ShouldUseWishAromatherapy(battlerAtk, battlerDef, move))
ADJUST_SCORE(DECENT_EFFECT);
break;
case EFFECT_PURIFY:
if (gBattleMons[battlerDef].status1 & STATUS1_ANY)
{
if (ShouldCureStatus(battlerAtk, battlerDef, aiData))
ADJUST_SCORE(GOOD_EFFECT);
if (ShouldRecover(battlerAtk, battlerDef, move, 50))
RETURN_SCORE_PLUS(WEAK_EFFECT);
}
break;
case EFFECT_CURSE:
if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GHOST))
{
@ -5909,6 +5931,17 @@ static s32 AI_CalcAdditionalEffectScore(u32 battlerAtk, u32 battlerDef, u32 move
if (ShouldSetScreen(battlerAtk, battlerDef, EFFECT_AURORA_VEIL))
ADJUST_SCORE(DECENT_EFFECT);
break;
case MOVE_EFFECT_REMOVE_STATUS:
if (gBattleMons[battlerDef].status1 & GetMoveEffectArg_Status(move))
{
if (ShouldCureStatus(battlerAtk, battlerDef, aiData))
ADJUST_SCORE(DECENT_EFFECT);
else if (aiData->holdEffects[battlerDef] == HOLD_EFFECT_FLAME_ORB || aiData->holdEffects[battlerDef] == HOLD_EFFECT_TOXIC_ORB)
ADJUST_SCORE(WEAK_EFFECT);
else
ADJUST_SCORE(BAD_EFFECT);
}
break;
default:
break;
}

View File

@ -2505,18 +2505,13 @@ static bool32 ShouldUseItem(u32 battler)
shouldUse = AI_ShouldHeal(battler, healAmount);
break;
case EFFECT_ITEM_CURE_STATUS:
if (itemEffects[3] & ITEM3_SLEEP && gBattleMons[battler].status1 & STATUS1_SLEEP)
shouldUse = TRUE;
if (itemEffects[3] & ITEM3_POISON && gBattleMons[battler].status1 & STATUS1_PSN_ANY)
shouldUse = TRUE;
if (itemEffects[3] & ITEM3_BURN && gBattleMons[battler].status1 & STATUS1_BURN)
shouldUse = TRUE;
if (itemEffects[3] & ITEM3_FREEZE && gBattleMons[battler].status1 & STATUS1_ICY_ANY)
shouldUse = TRUE;
if (itemEffects[3] & ITEM3_PARALYSIS && gBattleMons[battler].status1 & STATUS1_PARALYSIS)
shouldUse = TRUE;
if (itemEffects[3] & ITEM3_CONFUSION && gBattleMons[battler].volatiles.confusionTurns > 0)
shouldUse = TRUE;
if ((itemEffects[3] & ITEM3_SLEEP && gBattleMons[battler].status1 & STATUS1_SLEEP)
|| (itemEffects[3] & ITEM3_POISON && gBattleMons[battler].status1 & STATUS1_PSN_ANY)
|| (itemEffects[3] & ITEM3_BURN && gBattleMons[battler].status1 & STATUS1_BURN)
|| (itemEffects[3] & ITEM3_FREEZE && gBattleMons[battler].status1 & STATUS1_ICY_ANY)
|| (itemEffects[3] & ITEM3_PARALYSIS && gBattleMons[battler].status1 & STATUS1_PARALYSIS)
|| (itemEffects[3] & ITEM3_CONFUSION && gBattleMons[battler].volatiles.confusionTurns > 0))
shouldUse = ShouldCureStatusWithItem(battler, battler, gAiLogicData);
break;
case EFFECT_ITEM_INCREASE_STAT:
case EFFECT_ITEM_INCREASE_ALL_STATS:

View File

@ -3931,6 +3931,102 @@ bool32 ShouldSetScreen(u32 battlerAtk, u32 battlerDef, enum BattleMoveEffects mo
return FALSE;
}
static bool32 ShouldCureStatusInternal(u32 battlerAtk, u32 battlerDef, bool32 usingItem, struct AiLogicData *aiData)
{
bool32 targetingSelf = (battlerAtk == battlerDef);
bool32 targetingAlly = IsTargetingPartner(battlerAtk, battlerDef);
u32 status = gBattleMons[battlerDef].status1;
if (status & STATUS1_SLEEP)
{
if (targetingAlly || targetingSelf)
{
if (HasMoveWithEffect(battlerDef, EFFECT_SLEEP_TALK) || HasMoveWithEffect(battlerDef, EFFECT_SNORE))
return FALSE;
else
return usingItem || targetingAlly;
}
return FALSE;
}
if (status & STATUS1_FREEZE)
{
if (targetingAlly || targetingSelf)
{
if (HasThawingMove(battlerDef))
return FALSE;
return usingItem || targetingAlly;
}
return FALSE;
}
bool32 isHarmless = FALSE;
if (DoesBattlerBenefitFromAllVolatileStatus(battlerDef, aiData->abilities[battlerDef]))
isHarmless = TRUE;
if (status & STATUS1_PSN_ANY)
{
if (aiData->holdEffects[battlerDef] == HOLD_EFFECT_TOXIC_ORB)
return FALSE;
if (aiData->abilities[battlerDef] == ABILITY_POISON_HEAL)
isHarmless = TRUE;
if (aiData->abilities[battlerDef] == ABILITY_TOXIC_BOOST && !isHarmless)
{
if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL))
isHarmless = TRUE;
else if (!(targetingSelf || targetingAlly) && !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL))
isHarmless = TRUE;
}
}
if (status & STATUS1_BURN)
{
if (aiData->holdEffects[battlerDef] == HOLD_EFFECT_FLAME_ORB)
return FALSE;
if (aiData->abilities[battlerDef] == ABILITY_FLARE_BOOST && !isHarmless)
{
if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL))
isHarmless = TRUE;
else if (!(targetingSelf || targetingAlly) && !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL))
isHarmless = TRUE;
}
}
/*
if (status & STATUS1_PARALYSIS)
if (status & STATUS1_FROSTBITE)
*/
if (isHarmless)
{
if (targetingSelf || targetingAlly)
return FALSE;
else
return TRUE;
}
else
{
if (targetingSelf || targetingAlly)
return TRUE;
else
return FALSE;
}
}
bool32 ShouldCureStatus(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData)
{
return ShouldCureStatusInternal(battlerAtk, battlerDef, FALSE, aiData);
}
bool32 ShouldCureStatusWithItem(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData)
{
return ShouldCureStatusInternal(battlerAtk, battlerDef, TRUE, aiData);
}
// Partner Logic
bool32 IsBattle1v1()
{

View File

@ -439,3 +439,22 @@ AI_DOUBLE_BATTLE_TEST("AI sees type-changing moves as the correct type")
TURN { NOT_EXPECT_MOVE(opponentLeft, fieldStatus); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Sparkling Aria to cure an enemy with Guts")
{
u32 ability;
PARAMETRIZE { ability = ABILITY_GUTS; }
PARAMETRIZE { ability = ABILITY_BULLETPROOF; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_URSALUNA) { Ability(ability); Moves(MOVE_HEADLONG_RUSH, MOVE_CELEBRATE); Status1(STATUS1_BURN); }
OPPONENT(SPECIES_PRIMARINA) { Moves(MOVE_SPARKLING_ARIA, MOVE_SCALD); }
} WHEN {
if (ability == ABILITY_GUTS)
TURN { EXPECT_MOVE(opponent, MOVE_SPARKLING_ARIA); }
else
TURN { EXPECT_MOVE(opponent, MOVE_SCALD); }
}
}

View File

@ -22,5 +22,45 @@ AI_DOUBLE_BATTLE_TEST("AI uses Purify")
}
}
AI_SINGLE_BATTLE_TEST("AI uses Purify to heal an enemy with Guts")
{
u32 ability;
PARAMETRIZE { ability = ABILITY_GUTS; }
PARAMETRIZE { ability = ABILITY_BULLETPROOF; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_URSALUNA) { Ability(ability); Moves(MOVE_HEADLONG_RUSH, MOVE_CELEBRATE); Status1(STATUS1_BURN); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HEADBUTT, MOVE_PURIFY); }
} WHEN {
if (ability == ABILITY_GUTS)
TURN { EXPECT_MOVE(opponent, MOVE_PURIFY); }
else
TURN { NOT_EXPECT_MOVE(opponent, MOVE_PURIFY); }
}
}
AI_DOUBLE_BATTLE_TEST("AI does not use Purify to heal an ally with Guts")
{
u32 ability;
PARAMETRIZE { ability = ABILITY_GUTS; }
PARAMETRIZE { ability = ABILITY_BULLETPROOF; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HEADBUTT, MOVE_PURIFY); }
OPPONENT(SPECIES_URSALUNA) { Ability(ability); Moves(MOVE_HEADLONG_RUSH); Status1(STATUS1_BURN); }
} WHEN {
if (ability == ABILITY_GUTS)
TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_PURIFY); }
else
TURN { EXPECT_MOVE(opponentLeft, MOVE_PURIFY, target: opponentRight); }
}
}
TO_DO_BATTLE_TEST("TODO: Write Purify (Move Effect) test titles")
TO_DO_BATTLE_TEST("Purify doesn't heal HP if the target has Comatose")