Improve AI's ShouldRecover (#7342)
This commit is contained in:
parent
0406caa687
commit
65a63fb9f0
@ -93,7 +93,7 @@ bool32 AI_CanBattlerEscape(u32 battler);
|
||||
bool32 IsBattlerTrapped(u32 battlerAtk, u32 battlerDef);
|
||||
s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler2, u32 moveConsidered);
|
||||
bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk);
|
||||
u32 NoOfHitsForTargetToFaintAI(u32 battlerDef, u32 battlerAtk);
|
||||
u32 NoOfHitsForTargetToFaintBattler(u32 battlerDef, u32 battlerAtk);
|
||||
u32 GetBestDmgMoveFromBattler(u32 battlerAtk, u32 battlerDef, enum DamageCalcContext calcContext);
|
||||
u32 GetBestDmgFromBattler(u32 battler, u32 battlerTarget, enum DamageCalcContext calcContext);
|
||||
bool32 CanTargetMoveFaintAi(u32 move, u32 battlerDef, u32 battlerAtk, u32 nHits);
|
||||
@ -115,7 +115,7 @@ bool32 ShouldTryOHKO(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbil
|
||||
bool32 ShouldUseRecoilMove(u32 battlerAtk, u32 battlerDef, u32 recoilDmg, u32 moveIndex);
|
||||
u32 GetBattlerSideSpeedAverage(u32 battler);
|
||||
bool32 ShouldAbsorb(u32 battlerAtk, u32 battlerDef, u32 move, s32 damage);
|
||||
bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent, enum DamageCalcContext calcContext);
|
||||
bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent);
|
||||
bool32 ShouldSetScreen(u32 battlerAtk, u32 battlerDef, enum BattleMoveEffects moveEffect);
|
||||
enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 moveIndex);
|
||||
bool32 IsRecycleEncouragedItem(u32 item);
|
||||
|
||||
@ -56,6 +56,8 @@
|
||||
// AI move scoring
|
||||
#define STATUS_MOVE_FOCUS_PUNCH_CHANCE 50 // Chance the AI will use a status move if the player's best move is Focus Punch
|
||||
#define BOOST_INTO_HAZE_CHANCE 0 // Chance the AI will use a stat boosting move if the player has used Haze
|
||||
#define SHOULD_RECOVER_CHANCE 50 // Chance the AI will give recovery moves score increase if less than ENABLE_RECOVERY_THRESHOLD and in no immediate danger
|
||||
#define ENABLE_RECOVERY_THRESHOLD 60 // HP percentage beneath which SHOULD_RECOVER_CHANCE is active
|
||||
|
||||
// AI damage calc considerations
|
||||
#define RISKY_AI_CRIT_STAGE_THRESHOLD 2 // Stat stages at which Risky will assume it gets a crit
|
||||
|
||||
@ -200,6 +200,7 @@ enum RandomTag
|
||||
RNG_AI_PREDICT_MOVE,
|
||||
RNG_AI_STATUS_FOCUS_PUNCH,
|
||||
RNG_AI_BOOST_INTO_HAZE,
|
||||
RNG_AI_SHOULD_RECOVER,
|
||||
RNG_HEALER,
|
||||
RNG_DEXNAV_ENCOUNTER_LEVEL,
|
||||
RNG_AI_ASSUME_STATUS_SLEEP,
|
||||
|
||||
@ -3897,11 +3897,22 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
break;
|
||||
case EFFECT_DREAM_EATER:
|
||||
case EFFECT_STRENGTH_SAP:
|
||||
case EFFECT_AQUA_RING:
|
||||
if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT)
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
break;
|
||||
case EFFECT_STRENGTH_SAP:
|
||||
u32 atkStat = gBattleMons[battlerDef].attack;
|
||||
u32 atkStage = gBattleMons[battlerDef].statStages[STAT_ATK];
|
||||
atkStat *= gStatStageRatios[atkStage][0];
|
||||
atkStat /= gStatStageRatios[atkStage][1];
|
||||
u32 healPercent = atkStat * 100 / gBattleMons[battlerAtk].maxHP;
|
||||
if (ShouldRecover(battlerAtk, battlerDef, move, healPercent))
|
||||
{
|
||||
ADJUST_SCORE(GOOD_EFFECT);
|
||||
if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT)
|
||||
ADJUST_SCORE(WEAK_EFFECT);
|
||||
}
|
||||
case EFFECT_EXPLOSION:
|
||||
case EFFECT_MISTY_EXPLOSION:
|
||||
case EFFECT_MEMENTO:
|
||||
@ -4054,7 +4065,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
||||
break;
|
||||
}
|
||||
|
||||
if (ShouldRecover(battlerAtk, battlerDef, move, healPercent, AI_DEFENDING))
|
||||
if (ShouldRecover(battlerAtk, battlerDef, move, healPercent))
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
}
|
||||
break;
|
||||
@ -4064,7 +4075,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
||||
case EFFECT_MORNING_SUN:
|
||||
case EFFECT_SYNTHESIS:
|
||||
case EFFECT_MOONLIGHT:
|
||||
if (ShouldRecover(battlerAtk, battlerDef, move, 50, AI_DEFENDING))
|
||||
if (ShouldRecover(battlerAtk, battlerDef, move, 50))
|
||||
ADJUST_SCORE(GOOD_EFFECT);
|
||||
break;
|
||||
case EFFECT_LIGHT_SCREEN:
|
||||
@ -4082,7 +4093,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (ShouldRecover(battlerAtk, battlerDef, move, 100, AI_DEFENDING))
|
||||
else if (ShouldRecover(battlerAtk, battlerDef, move, 100))
|
||||
{
|
||||
if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_CURE_SLP
|
||||
|| aiData->holdEffects[battlerAtk] == HOLD_EFFECT_CURE_STATUS
|
||||
@ -5044,9 +5055,9 @@ case EFFECT_GUARD_SPLIT:
|
||||
ADJUST_SCORE(GOOD_EFFECT);
|
||||
break;
|
||||
case EFFECT_SHORE_UP:
|
||||
if ((AI_GetWeather() & B_WEATHER_SANDSTORM) && ShouldRecover(battlerAtk, battlerDef, move, 67, AI_DEFENDING))
|
||||
if ((AI_GetWeather() & B_WEATHER_SANDSTORM) && ShouldRecover(battlerAtk, battlerDef, move, 67))
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
else if (ShouldRecover(battlerAtk, battlerDef, move, 50, AI_DEFENDING))
|
||||
else if (ShouldRecover(battlerAtk, battlerDef, move, 50))
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
break;
|
||||
case EFFECT_ENDEAVOR:
|
||||
@ -5072,8 +5083,8 @@ case EFFECT_GUARD_SPLIT:
|
||||
//case EFFECT_SKY_DROP
|
||||
//break;
|
||||
case EFFECT_JUNGLE_HEALING:
|
||||
if (ShouldRecover(battlerAtk, battlerDef, move, 25, AI_DEFENDING)
|
||||
|| ShouldRecover(BATTLE_PARTNER(battlerAtk), battlerDef, move, 25, AI_DEFENDING)
|
||||
if (ShouldRecover(battlerAtk, battlerDef, move, 25)
|
||||
|| ShouldRecover(BATTLE_PARTNER(battlerAtk), battlerDef, move, 25)
|
||||
|| gBattleMons[battlerAtk].status1 & STATUS1_ANY
|
||||
|| gBattleMons[BATTLE_PARTNER(battlerAtk)].status1 & STATUS1_ANY)
|
||||
ADJUST_SCORE(GOOD_EFFECT);
|
||||
|
||||
@ -1349,7 +1349,7 @@ bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
u32 NoOfHitsForTargetToFaintAI(u32 battlerDef, u32 battlerAtk)
|
||||
u32 NoOfHitsForTargetToFaintBattler(u32 battlerDef, u32 battlerAtk)
|
||||
{
|
||||
u32 i;
|
||||
u32 currNumberOfHits;
|
||||
@ -1367,6 +1367,32 @@ u32 NoOfHitsForTargetToFaintAI(u32 battlerDef, u32 battlerAtk)
|
||||
return leastNumberOfHits;
|
||||
}
|
||||
|
||||
u32 NoOfHitsForTargetToFaintBattlerWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod)
|
||||
{
|
||||
u32 i;
|
||||
u32 currNumberOfHits;
|
||||
u32 leastNumberOfHits = UNKNOWN_NO_OF_HITS;
|
||||
u32 hpCheck = gBattleMons[battlerAtk].hp + hpMod;
|
||||
u32 damageDealt = 0;
|
||||
|
||||
if (hpCheck > gBattleMons[battlerAtk].maxHP)
|
||||
hpCheck = gBattleMons[battlerAtk].maxHP;
|
||||
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
damageDealt = AI_GetDamage(battlerDef, battlerAtk, i, AI_DEFENDING, gAiLogicData);
|
||||
if (damageDealt == 0)
|
||||
continue;
|
||||
currNumberOfHits = hpCheck / (damageDealt + 1) + 1;
|
||||
if (currNumberOfHits != 0)
|
||||
{
|
||||
if (currNumberOfHits < leastNumberOfHits)
|
||||
leastNumberOfHits = currNumberOfHits;
|
||||
}
|
||||
}
|
||||
return leastNumberOfHits;
|
||||
}
|
||||
|
||||
u32 GetBestDmgMoveFromBattler(u32 battlerAtk, u32 battlerDef, enum DamageCalcContext calcContext)
|
||||
{
|
||||
struct AiLogicData *aiData = gAiLogicData;
|
||||
@ -3710,21 +3736,30 @@ bool32 ShouldAbsorb(u32 battlerAtk, u32 battlerDef, u32 move, s32 damage)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent, enum DamageCalcContext calcContext)
|
||||
bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent)
|
||||
{
|
||||
if (move == 0xFFFF || AI_IsFaster(battlerAtk, battlerDef, move))
|
||||
u32 maxHP = gBattleMons[battlerAtk].maxHP;
|
||||
u32 healAmount = (healPercent * maxHP) / 100;
|
||||
if (healAmount > maxHP)
|
||||
healAmount = maxHP;
|
||||
if (gStatuses3[battlerAtk] & STATUS3_HEAL_BLOCK)
|
||||
healAmount = 0;
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move))
|
||||
{
|
||||
// using item or user going first
|
||||
s32 damage = AI_GetDamage(battlerAtk, battlerDef, gAiThinkingStruct->movesetIndex, calcContext, gAiLogicData);
|
||||
s32 healAmount = (healPercent * damage) / 100;
|
||||
if (gStatuses3[battlerAtk] & STATUS3_HEAL_BLOCK)
|
||||
healAmount = 0;
|
||||
|
||||
if (CanTargetFaintAi(battlerDef, battlerAtk)
|
||||
&& !CanTargetFaintAiWithMod(battlerDef, battlerAtk, healAmount, 0))
|
||||
return TRUE; // target can faint attacker unless they heal
|
||||
else if (!CanTargetFaintAi(battlerDef, battlerAtk) && gAiLogicData->hpPercents[battlerAtk] < 60 && (Random() % 3))
|
||||
return TRUE; // target can't faint attacker at all, attacker health is about half, 2/3rds rate of encouraging healing
|
||||
else if (!CanTargetFaintAi(battlerDef, battlerAtk) && gAiLogicData->hpPercents[battlerAtk] < ENABLE_RECOVERY_THRESHOLD && RandomPercentage(RNG_AI_SHOULD_RECOVER, SHOULD_RECOVER_CHANCE))
|
||||
return TRUE; // target can't faint attacker at all, generally safe
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!CanTargetFaintAi(battlerDef, battlerAtk)
|
||||
&& GetBestDmgFromBattler(battlerDef, battlerAtk, AI_DEFENDING) < healAmount
|
||||
&& NoOfHitsForTargetToFaintBattler(battlerDef, battlerAtk) < NoOfHitsForTargetToFaintBattlerWithMod(battlerDef, battlerAtk, healAmount))
|
||||
return TRUE; // target can't faint attacker and is dealing less damage than we're healing
|
||||
else if (!CanTargetFaintAi(battlerDef, battlerAtk) && gAiLogicData->hpPercents[battlerAtk] < ENABLE_RECOVERY_THRESHOLD && RandomPercentage(RNG_AI_SHOULD_RECOVER, SHOULD_RECOVER_CHANCE))
|
||||
return TRUE; // target can't faint attacker at all, generally safe
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
@ -3972,7 +4007,7 @@ bool32 ShouldUseWishAromatherapy(u32 battlerAtk, u32 battlerDef, u32 move)
|
||||
switch (GetMoveEffect(move))
|
||||
{
|
||||
case EFFECT_WISH:
|
||||
return ShouldRecover(battlerAtk, battlerDef, move, 50, AI_DEFENDING); // Switch recovery isn't good idea in doubles
|
||||
return ShouldRecover(battlerAtk, battlerDef, move, 50); // Switch recovery isn't good idea in doubles
|
||||
case EFFECT_HEAL_BELL:
|
||||
if (hasStatus)
|
||||
return TRUE;
|
||||
@ -4272,7 +4307,7 @@ bool32 HasMoveThatChangesKOThreshold(u32 battlerId, u32 noOfHitsToFaint, u32 aiI
|
||||
static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef, enum StatChange statId, bool32 considerContrary)
|
||||
{
|
||||
enum AIScore tempScore = NO_INCREASE;
|
||||
u32 noOfHitsToFaint = NoOfHitsForTargetToFaintAI(battlerDef, battlerAtk);
|
||||
u32 noOfHitsToFaint = NoOfHitsForTargetToFaintBattler(battlerDef, battlerAtk);
|
||||
u32 aiIsFaster = AI_IsFaster(battlerAtk, battlerDef, TRUE);
|
||||
u32 shouldSetUp = ((noOfHitsToFaint >= 2 && aiIsFaster) || (noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == UNKNOWN_NO_OF_HITS);
|
||||
u32 i;
|
||||
|
||||
@ -945,3 +945,42 @@ AI_SINGLE_BATTLE_TEST("AI won't setup if otherwise good scenario is changed by t
|
||||
TURN { MOVE(player, MOVE_SURF); EXPECT_MOVE(opponent, MOVE_EARTHQUAKE); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI will use Recovery move if it outheals your damage and outspeeds")
|
||||
{
|
||||
PASSES_RANDOMLY(100, 100, RNG_AI_SHOULD_RECOVER);
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
|
||||
PLAYER(SPECIES_LINOONE) { Speed(2); Moves(MOVE_HEADBUTT); }
|
||||
OPPONENT(SPECIES_GASTRODON) { Speed(5); Moves(MOVE_SCALD, MOVE_RECOVER); HP(1); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_HEADBUTT); EXPECT_MOVE(opponent, MOVE_RECOVER); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI will use recovery move if it outheals your damage and is outsped")
|
||||
{
|
||||
u32 aiMove = MOVE_NONE;
|
||||
PASSES_RANDOMLY(100, 100, RNG_AI_SHOULD_RECOVER);
|
||||
PARAMETRIZE{ aiMove = MOVE_RECOVER; }
|
||||
PARAMETRIZE{ aiMove = MOVE_STRENGTH_SAP; }
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
|
||||
PLAYER(SPECIES_LINOONE) { Speed(5); Moves(MOVE_TACKLE); }
|
||||
OPPONENT(SPECIES_GASTRODON) { Speed(2); Moves(MOVE_SCALD, aiMove); HP(200); MaxHP(400); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, aiMove); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI will use recovery move if is in no immediate danger beneath an HP threshold")
|
||||
{
|
||||
PASSES_RANDOMLY(SHOULD_RECOVER_CHANCE, 100, RNG_AI_SHOULD_RECOVER);
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
|
||||
PLAYER(SPECIES_LINOONE) { Speed(2); Moves(MOVE_TACKLE); }
|
||||
OPPONENT(SPECIES_GASTRODON) { Speed(5); Moves(MOVE_SCALD, MOVE_RECOVER); HP(200); MaxHP(400); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_RECOVER); }
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user