Stat stage related AI fixes (#4548)

* stat stage related AI fixes

* add more ai fixes and 2 tests

* use legal ability in tests

* Fix test and remove mold breaker check

* Use DoesBattlerIgnoreAbilityChecks
This commit is contained in:
sneed 2024-05-11 20:03:19 +03:00 committed by GitHub
parent 8de4ed5a7c
commit f502ba2a69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 179 additions and 45 deletions

View File

@ -32,13 +32,13 @@
#define BEST_DAMAGE_MOVE 1 // Move with the most amount of hits with the best accuracy/effect
#define POWERFUL_STATUS_MOVE 10 // Moves with this score will be chosen over a move that faints target
// Temporary scores that are added together to determine a final score at the at of AI_CalcMoveScore
// Temporary scores that are added together to determine a final score at the at of AI_CalcMoveEffectScore
#define WEAK_EFFECT 1
#define DECENT_EFFECT 2
#define GOOD_EFFECT 4
#define BEST_EFFECT 6
// AI_CalcMoveScore final score
// AI_CalcMoveEffectScore final score
#define NOT_GOOD_ENOUGH 0 // Not worth using over a damaging move
#define GOOD_MOVE_EFFECTS 2 // Worth using over a damaging move
#define PREFERRED_MOVE_EFFECTS 3 // Worth using over a damagin move and is better then DECENT_EFFECT

View File

@ -179,6 +179,7 @@ bool32 SideHasMoveCategory(u32 battlerId, u32 category);
// score increases
void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score);
void IncreaseStatUpScoreContrary(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score);
void IncreasePoisonScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score);
void IncreaseBurnScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score);
void IncreaseParalyzeScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score);

View File

@ -3161,7 +3161,7 @@ static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId)
return score;
}
static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move)
static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
{
// move data
u32 moveEffect = gMovesInfo[move].effect;
@ -3592,7 +3592,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move)
else
ADJUST_SCORE(WEAK_EFFECT);
}
else if (aiData->abilities[battlerAtk] != ABILITY_CONTRARY)
else
{
IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score);
IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score);
@ -4407,56 +4407,88 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move)
// Consider move effects that target self
if (gMovesInfo[move].additionalEffects[i].self)
{
u32 oneStageStatId = STAT_CHANGE_ATK + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1;
u32 twoStageStatId = STAT_CHANGE_ATK_2 + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1;
u32 StageStatId;
switch (gMovesInfo[move].additionalEffects[i].moveEffect)
if (aiData->abilities[battlerAtk] != ABILITY_CONTRARY)
{
case MOVE_EFFECT_SPD_PLUS_2:
case MOVE_EFFECT_SPD_PLUS_1:
if (aiData->abilities[battlerAtk] != ABILITY_CONTRARY && !AI_STRIKES_FIRST(battlerAtk, battlerDef, move))
ADJUST_SCORE(GOOD_EFFECT);
break;
switch (gMovesInfo[move].additionalEffects[i].moveEffect)
{
case MOVE_EFFECT_ATK_PLUS_1:
case MOVE_EFFECT_DEF_PLUS_1:
case MOVE_EFFECT_SPD_PLUS_1:
case MOVE_EFFECT_SP_ATK_PLUS_1:
case MOVE_EFFECT_SP_DEF_PLUS_1:
case MOVE_EFFECT_ACC_PLUS_1:
case MOVE_EFFECT_EVS_PLUS_1:
IncreaseStatUpScore(battlerAtk, battlerDef, oneStageStatId, &score);
StageStatId = STAT_CHANGE_ATK + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1;
IncreaseStatUpScore(battlerAtk, battlerDef, StageStatId, &score);
break;
case MOVE_EFFECT_ATK_PLUS_2:
case MOVE_EFFECT_DEF_PLUS_2:
case MOVE_EFFECT_SPD_PLUS_2:
case MOVE_EFFECT_SP_ATK_PLUS_2:
case MOVE_EFFECT_SP_DEF_PLUS_2:
case MOVE_EFFECT_ACC_PLUS_2:
case MOVE_EFFECT_EVS_PLUS_2:
IncreaseStatUpScore(battlerAtk, battlerDef, twoStageStatId, &score);
StageStatId = STAT_CHANGE_ATK_2 + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1;
IncreaseStatUpScore(battlerAtk, battlerDef, StageStatId, &score);
break;
// Effects that lower stat(s) - only need to consider Contrary
case MOVE_EFFECT_ATK_MINUS_1:
case MOVE_EFFECT_DEF_MINUS_1:
case MOVE_EFFECT_SPD_MINUS_1:
case MOVE_EFFECT_SP_ATK_MINUS_1:
case MOVE_EFFECT_SP_DEF_MINUS_1:
case MOVE_EFFECT_DEF_SPDEF_DOWN:
case MOVE_EFFECT_ATK_DEF_DOWN:
case MOVE_EFFECT_SP_ATK_TWO_DOWN:
if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY)
IncreaseStatUpScore(battlerAtk, battlerDef, oneStageStatId, &score);
case MOVE_EFFECT_V_CREATE:
if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY)
{
IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score);
IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score);
IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score);
}
case MOVE_EFFECT_ACC_PLUS_1:
case MOVE_EFFECT_ACC_PLUS_2:
IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ACC, &score);
break;
case MOVE_EFFECT_EVS_PLUS_1:
case MOVE_EFFECT_EVS_PLUS_2:
IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_EVASION, &score);
break;
case MOVE_EFFECT_RAPID_SPIN:
if ((gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY && CountUsablePartyMons(battlerAtk) != 0)
|| (gStatuses3[battlerAtk] & STATUS3_LEECHSEED || gBattleMons[battlerAtk].status2 & STATUS2_WRAPPED))
ADJUST_SCORE(GOOD_EFFECT);
break;
}
}
else
{
switch (gMovesInfo[move].additionalEffects[i].moveEffect)
{
case MOVE_EFFECT_ATK_MINUS_1:
case MOVE_EFFECT_DEF_MINUS_1:
case MOVE_EFFECT_SPD_MINUS_1:
case MOVE_EFFECT_SP_ATK_MINUS_1:
case MOVE_EFFECT_SP_DEF_MINUS_1:
StageStatId = STAT_CHANGE_ATK + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_MINUS_1;
IncreaseStatUpScoreContrary(battlerAtk, battlerDef, StageStatId, &score);
break;
case MOVE_EFFECT_ATK_MINUS_2:
case MOVE_EFFECT_DEF_MINUS_2:
case MOVE_EFFECT_SPD_MINUS_2:
case MOVE_EFFECT_SP_ATK_MINUS_2:
case MOVE_EFFECT_SP_DEF_MINUS_2:
StageStatId = STAT_CHANGE_ATK + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_MINUS_2;
IncreaseStatUpScoreContrary(battlerAtk, battlerDef, StageStatId, &score);
break;
case MOVE_EFFECT_ACC_MINUS_1:
case MOVE_EFFECT_ACC_MINUS_2:
IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_ACC, &score);
break;
case MOVE_EFFECT_EVS_MINUS_1:
case MOVE_EFFECT_EVS_MINUS_2:
IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_EVASION, &score);
break;
case MOVE_EFFECT_DEF_SPDEF_DOWN:
IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score);
IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score);
break;
case MOVE_EFFECT_ATK_DEF_DOWN:
IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_ATK, &score);
IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score);
break;
case MOVE_EFFECT_SP_ATK_TWO_DOWN:
IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_SPATK_2, &score);
break;
case MOVE_EFFECT_V_CREATE:
IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_DEF, &score);
IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_SPEED, &score);
IncreaseStatUpScoreContrary(battlerAtk, battlerDef, STAT_CHANGE_SPDEF, &score);
break;
}
}
}
else // consider move effects that hinder the target
@ -4639,8 +4671,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
score += AI_CompareDamagingMoves(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex);
}
// Calculates score based on effects of a move
score += AI_CalcMoveScore(battlerAtk, battlerDef, move);
score += AI_CalcMoveEffectScore(battlerAtk, battlerDef, move);
return score;
}

View File

@ -627,22 +627,35 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3
switch (gMovesInfo[move].additionalEffects[i].moveEffect)
{
case MOVE_EFFECT_ATK_PLUS_1:
case MOVE_EFFECT_ATK_PLUS_2:
if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ATK))
return TRUE;
break;
case MOVE_EFFECT_DEF_PLUS_2:
case MOVE_EFFECT_DEF_PLUS_1:
case MOVE_EFFECT_DEF_PLUS_2:
if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_DEF))
return TRUE;
break;
case MOVE_EFFECT_SPD_PLUS_1:
case MOVE_EFFECT_SPD_PLUS_2:
if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPEED))
return TRUE;
break;
case MOVE_EFFECT_SP_ATK_PLUS_1:
case MOVE_EFFECT_SP_ATK_PLUS_2:
if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPATK))
return TRUE;
break;
case MOVE_EFFECT_EVS_PLUS_1:
case MOVE_EFFECT_EVS_PLUS_2:
if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_EVASION))
return TRUE;
break;
case MOVE_EFFECT_ACC_PLUS_1:
case MOVE_EFFECT_ACC_PLUS_2:
if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ACC))
return TRUE;
break;
case MOVE_EFFECT_ALL_STATS_UP:
for (i = STAT_ATK; i <= NUM_STATS; i++)
{
@ -737,18 +750,45 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s
case MOVE_EFFECT_DEF_MINUS_1:
case MOVE_EFFECT_SPD_MINUS_1:
case MOVE_EFFECT_SP_ATK_MINUS_1:
case MOVE_EFFECT_SP_DEF_MINUS_1:
case MOVE_EFFECT_EVS_MINUS_1:
case MOVE_EFFECT_ACC_MINUS_1:
case MOVE_EFFECT_ATK_MINUS_2:
case MOVE_EFFECT_DEF_MINUS_2:
case MOVE_EFFECT_SPD_MINUS_2:
case MOVE_EFFECT_SP_ATK_MINUS_2:
case MOVE_EFFECT_SP_DEF_MINUS_2:
case MOVE_EFFECT_EVS_MINUS_2:
case MOVE_EFFECT_ACC_MINUS_2:
case MOVE_EFFECT_SP_ATK_TWO_DOWN:
case MOVE_EFFECT_V_CREATE:
case MOVE_EFFECT_ATK_DEF_DOWN:
case MOVE_EFFECT_DEF_SPDEF_DOWN:
case MOVE_EFFECT_SP_DEF_MINUS_1:
case MOVE_EFFECT_SP_DEF_MINUS_2:
if ((gMovesInfo[move].additionalEffects[i].self && GetBattlerAbility(battlerAtk) != ABILITY_CONTRARY)
|| (noOfHitsToKo != 1 && abilityDef == ABILITY_CONTRARY && !IsMoldBreakerTypeAbility(abilityAtk)))
if ((gMovesInfo[move].additionalEffects[i].self && abilityAtk != ABILITY_CONTRARY)
|| (noOfHitsToKo != 1 && abilityDef == ABILITY_CONTRARY && !DoesBattlerIgnoreAbilityChecks(abilityAtk, move)))
return TRUE;
break;
case MOVE_EFFECT_RECHARGE:
return gMovesInfo[move].additionalEffects[i].self;
case MOVE_EFFECT_ATK_PLUS_1:
case MOVE_EFFECT_DEF_PLUS_1:
case MOVE_EFFECT_SPD_PLUS_1:
case MOVE_EFFECT_SP_ATK_PLUS_1:
case MOVE_EFFECT_SP_DEF_PLUS_1:
case MOVE_EFFECT_EVS_PLUS_1:
case MOVE_EFFECT_ACC_PLUS_1:
case MOVE_EFFECT_ATK_PLUS_2:
case MOVE_EFFECT_DEF_PLUS_2:
case MOVE_EFFECT_SPD_PLUS_2:
case MOVE_EFFECT_SP_ATK_PLUS_2:
case MOVE_EFFECT_SP_DEF_PLUS_2:
case MOVE_EFFECT_EVS_PLUS_2:
case MOVE_EFFECT_ACC_PLUS_2:
case MOVE_EFFECT_ALL_STATS_UP:
if ((gMovesInfo[move].additionalEffects[i].self && abilityAtk == ABILITY_CONTRARY)
|| (noOfHitsToKo != 1 && !(abilityDef == ABILITY_CONTRARY && !DoesBattlerIgnoreAbilityChecks(abilityAtk, move))))
return TRUE;
break;
}
}
break;
@ -3388,13 +3428,13 @@ bool32 IsRecycleEncouragedItem(u32 item)
return FALSE;
}
void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score)
static void IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score, bool32 considerContrary)
{
u32 noOfHitsToFaint = NoOfHitsForTargetToFaintAI(battlerDef, battlerAtk);
u32 aiIsFaster = GetWhichBattlerFaster(battlerAtk, battlerDef, TRUE) == AI_IS_FASTER;
u32 shouldSetUp = ((noOfHitsToFaint >= 2 && aiIsFaster) || (noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == UNKNOWN_NO_OF_HITS);
if (AI_DATA->abilities[battlerAtk] == ABILITY_CONTRARY)
if (considerContrary && AI_DATA->abilities[battlerAtk] == ABILITY_CONTRARY)
return;
// Don't increase stat if AI is at +4
@ -3488,6 +3528,16 @@ void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score)
}
}
void IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score)
{
IncreaseStatUpScoreInternal(battlerAtk, battlerDef, statId, score, TRUE);
}
void IncreaseStatUpScoreContrary(u32 battlerAtk, u32 battlerDef, u32 statId, s32 *score)
{
IncreaseStatUpScoreInternal(battlerAtk, battlerDef, statId, score, FALSE);
}
void IncreasePoisonScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score)
{
if (((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))

View File

@ -830,3 +830,55 @@ AI_SINGLE_BATTLE_TEST("AI will choose Surf over Thunderbolt and Ice Beam if the
TURN { EXPECT_MOVE(opponent, MOVE_SURF); }
}
}
AI_SINGLE_BATTLE_TEST("AI will choose Scratch over Power-up Punch with Contrary")
{
u32 ability;
PARAMETRIZE {ability = ABILITY_SUCTION_CUPS; }
PARAMETRIZE {ability = ABILITY_CONTRARY; }
GIVEN {
ASSUME(gMovesInfo[MOVE_SCRATCH].power == 40);
ASSUME(gMovesInfo[MOVE_SCRATCH].type == TYPE_NORMAL);
ASSUME(gMovesInfo[MOVE_POWER_UP_PUNCH].power == 40);
ASSUME(gMovesInfo[MOVE_POWER_UP_PUNCH].type == TYPE_FIGHTING);
ASSUME(gSpeciesInfo[SPECIES_SQUIRTLE].types[0] == TYPE_WATER);
ASSUME(gSpeciesInfo[SPECIES_SQUIRTLE].types[1] == TYPE_WATER);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_SQUIRTLE) { };
OPPONENT(SPECIES_MALAMAR) { Ability(ability); Moves(MOVE_SCRATCH, MOVE_POWER_UP_PUNCH); }
} WHEN {
TURN {
if (ability != ABILITY_CONTRARY)
EXPECT_MOVE(opponent, MOVE_POWER_UP_PUNCH);
else
EXPECT_MOVE(opponent, MOVE_SCRATCH);
}
}
}
AI_SINGLE_BATTLE_TEST("AI will choose Superpower over Outrage with Contrary")
{
u32 ability;
PARAMETRIZE {ability = ABILITY_SUCTION_CUPS; }
PARAMETRIZE {ability = ABILITY_CONTRARY; }
GIVEN {
ASSUME(gMovesInfo[MOVE_SUPERPOWER].power == 120);
ASSUME(gMovesInfo[MOVE_SUPERPOWER].type == TYPE_FIGHTING);
ASSUME(gMovesInfo[MOVE_OUTRAGE].power == 120);
ASSUME(gMovesInfo[MOVE_OUTRAGE].type == TYPE_DRAGON);
ASSUME(gSpeciesInfo[SPECIES_SQUIRTLE].types[0] == TYPE_WATER);
ASSUME(gSpeciesInfo[SPECIES_SQUIRTLE].types[1] == TYPE_WATER);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_SQUIRTLE) { };
OPPONENT(SPECIES_MALAMAR) { Ability(ability); Moves(MOVE_OUTRAGE, MOVE_SUPERPOWER); }
} WHEN {
TURN {
if (ability != ABILITY_CONTRARY)
EXPECT_MOVE(opponent, MOVE_OUTRAGE);
else
EXPECT_MOVE(opponent, MOVE_SUPERPOWER);
}
}
}