Adds ability Minds Eye + Tests (#3782)

* Implemented Mind's Eye with tests + Keen Eye tests

* AI test

* fix AI minds eye test

* Adds ability Minds Eye + Tests

* fix tests

---------

Co-authored-by: Eduardo Quezada <eduardo602002@gmail.com>
Co-authored-by: Bassoonian <iasperbassoonian@gmail.com>
This commit is contained in:
Alex 2023-12-22 16:22:53 +01:00 committed by GitHub
parent 566653aa63
commit 83e2f4bdf8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 288 additions and 8 deletions

View file

@ -877,6 +877,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break;
// fallthrough
case ABILITY_KEEN_EYE:
case ABILITY_MINDS_EYE:
if (moveEffect == EFFECT_ACCURACY_DOWN || moveEffect == EFFECT_ACCURACY_DOWN_2)
RETURN_SCORE_MINUS(10);
break;
@ -1299,7 +1300,8 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_ACCURACY_DOWN_2:
if (!ShouldLowerStat(battlerDef, aiData->abilities[battlerDef], STAT_ACC))
ADJUST_SCORE(-10);
else if (aiData->abilities[battlerDef] == ABILITY_KEEN_EYE || (B_ILLUMINATE_EFFECT >= GEN_9 && aiData->abilities[battlerDef] == ABILITY_ILLUMINATE))
else if (aiData->abilities[battlerDef] == ABILITY_KEEN_EYE || aiData->abilities[battlerDef] == ABILITY_MINDS_EYE
|| (B_ILLUMINATE_EFFECT >= GEN_9 && aiData->abilities[battlerDef] == ABILITY_ILLUMINATE))
ADJUST_SCORE(-8);
break;
case EFFECT_EVASION_DOWN:
@ -3933,7 +3935,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
//TODO - track entire opponent party data to determine hazard effectiveness
break;
case EFFECT_FORESIGHT:
if (aiData->abilities[battlerAtk] == ABILITY_SCRAPPY)
if (aiData->abilities[battlerAtk] == ABILITY_SCRAPPY || aiData->abilities[battlerAtk] == ABILITY_MINDS_EYE)
break;
else if (gBattleMons[battlerDef].statStages[STAT_EVASION] > DEFAULT_STAT_STAGE
|| (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GHOST)

View file

@ -509,6 +509,7 @@ static bool32 ShouldSwitchIfGameStatePrompt(u32 battler, bool32 emitResult)
if (gBattleMons[battler].statStages[STAT_EVASION] > (DEFAULT_STAT_STAGE + 3)
&& AI_DATA->abilities[opposingBattler] != ABILITY_UNAWARE
&& AI_DATA->abilities[opposingBattler] != ABILITY_KEEN_EYE
&& AI_DATA->abilities[opposingBattler] != ABILITY_MINDS_EYE
&& (B_ILLUMINATE_EFFECT >= GEN_9 && AI_DATA->abilities[opposingBattler] != ABILITY_ILLUMINATE)
&& !(gBattleMons[battler].status2 & STATUS2_FORESIGHT)
&& !(gStatuses3[battler] & STATUS3_MIRACLE_EYED))

View file

@ -1855,6 +1855,7 @@ bool32 ShouldLowerAccuracy(u32 battlerAtk, u32 battlerDef, u32 defAbility)
&& defAbility != ABILITY_WHITE_SMOKE
&& defAbility != ABILITY_FULL_METAL_BODY
&& defAbility != ABILITY_KEEN_EYE
&& defAbility != ABILITY_MINDS_EYE
&& (B_ILLUMINATE_EFFECT >= GEN_9 && defAbility != ABILITY_ILLUMINATE)
&& AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_CLEAR_AMULET)
return TRUE;

View file

@ -1547,7 +1547,7 @@ static bool32 AccuracyCalcHelper(u16 move)
return TRUE;
}
// If the attacker has the ability No Guard and they aren't targeting a Pokemon involved in a Sky Drop with the move Sky Drop, move hits.
else if (GetBattlerAbility(gBattlerAttacker) == ABILITY_NO_GUARD && (move != MOVE_SKY_DROP || gBattleStruct->skyDropTargets[gBattlerTarget] == 0xFF))
else if (GetBattlerAbility(gBattlerTarget) == ABILITY_NO_GUARD && (move != MOVE_SKY_DROP || gBattleStruct->skyDropTargets[gBattlerTarget] == 0xFF))
{
if (!JumpIfMoveFailed(7, move))
RecordAbilityBattle(gBattlerAttacker, ABILITY_NO_GUARD);
@ -1636,7 +1636,8 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u
gPotentialItemEffectBattler = battlerDef;
accStage = gBattleMons[battlerAtk].statStages[STAT_ACC];
evasionStage = gBattleMons[battlerDef].statStages[STAT_EVASION];
if (atkAbility == ABILITY_UNAWARE || atkAbility == ABILITY_KEEN_EYE || (B_ILLUMINATE_EFFECT >= GEN_9 && atkAbility == ABILITY_ILLUMINATE))
if (atkAbility == ABILITY_UNAWARE || atkAbility == ABILITY_KEEN_EYE || atkAbility == ABILITY_MINDS_EYE
|| (B_ILLUMINATE_EFFECT >= GEN_9 && atkAbility == ABILITY_ILLUMINATE))
evasionStage = DEFAULT_STAT_STAGE;
if (gBattleMoves[move].ignoresTargetDefenseEvasionStages)
evasionStage = DEFAULT_STAT_STAGE;
@ -5080,7 +5081,7 @@ static void Cmd_playstatchangeanimation(void)
&& ability != ABILITY_CLEAR_BODY
&& ability != ABILITY_FULL_METAL_BODY
&& ability != ABILITY_WHITE_SMOKE
&& !(ability == ABILITY_KEEN_EYE && currStat == STAT_ACC)
&& !((ability == ABILITY_KEEN_EYE || ability == ABILITY_MINDS_EYE) && currStat == STAT_ACC)
&& !(B_ILLUMINATE_EFFECT >= GEN_9 && ability == ABILITY_ILLUMINATE && currStat == STAT_ACC)
&& !(ability == ABILITY_HYPER_CUTTER && currStat == STAT_ATK)
&& !(ability == ABILITY_BIG_PECKS && currStat == STAT_DEF))
@ -11355,7 +11356,7 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
return STAT_CHANGE_DIDNT_WORK;
}
else if (!certain
&& ((battlerAbility == ABILITY_KEEN_EYE && statId == STAT_ACC)
&& (((battlerAbility == ABILITY_KEEN_EYE || battlerAbility == ABILITY_MINDS_EYE) && statId == STAT_ACC)
|| (B_ILLUMINATE_EFFECT >= GEN_9 && battlerAbility == ABILITY_ILLUMINATE && statId == STAT_ACC)
|| (battlerAbility == ABILITY_HYPER_CUTTER && statId == STAT_ATK)
|| (battlerAbility == ABILITY_BIG_PECKS && statId == STAT_DEF)))

View file

@ -976,6 +976,8 @@ static const u8 sAbilitiesAffectedByMoldBreaker[] =
[ABILITY_GOOD_AS_GOLD] = 1,
[ABILITY_PURIFYING_SALT] = 1,
[ABILITY_WELL_BAKED_BODY] = 1,
[ABILITY_MINDS_EYE] = 1,
[ABILITY_ILLUMINATE] = 1,
};
static const u8 sAbilitiesNotTraced[ABILITIES_COUNT] =
@ -9984,6 +9986,7 @@ s32 CalculateMoveDamageVars(u32 move, u32 battlerAtk, u32 battlerDef, u32 moveTy
static inline void MulByTypeEffectiveness(uq4_12_t *modifier, u32 move, u32 moveType, u32 battlerDef, u32 defType, u32 battlerAtk, bool32 recordAbilities)
{
uq4_12_t mod = GetTypeModifier(moveType, defType);
u32 abilityAtk = GetBattlerAbility(battlerAtk);
if (mod == UQ_4_12(0.0) && GetBattlerHoldEffect(battlerDef, TRUE) == HOLD_EFFECT_RING_TARGET)
{
@ -9995,11 +9998,13 @@ static inline void MulByTypeEffectiveness(uq4_12_t *modifier, u32 move, u32 move
{
mod = UQ_4_12(1.0);
}
else if ((moveType == TYPE_FIGHTING || moveType == TYPE_NORMAL) && defType == TYPE_GHOST && GetBattlerAbility(battlerAtk) == ABILITY_SCRAPPY && mod == UQ_4_12(0.0))
else if ((moveType == TYPE_FIGHTING || moveType == TYPE_NORMAL) && defType == TYPE_GHOST
&& (abilityAtk == ABILITY_SCRAPPY || abilityAtk == ABILITY_MINDS_EYE)
&& mod == UQ_4_12(0.0))
{
mod = UQ_4_12(1.0);
if (recordAbilities)
RecordAbilityBattle(battlerAtk, ABILITY_SCRAPPY);
RecordAbilityBattle(battlerAtk, abilityAtk);
}
if (moveType == TYPE_PSYCHIC && defType == TYPE_DARK && gStatuses3[battlerDef] & STATUS3_MIRACLE_EYED && mod == UQ_4_12(0.0))

View file

@ -0,0 +1,199 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_TACKLE].accuracy == 100);
ASSUME(gBattleMoves[MOVE_SAND_ATTACK].effect == EFFECT_ACCURACY_DOWN);
ASSUME(B_ILLUMINATE_EFFECT >= GEN_9);
}
SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye prevent accuracy stage reduction from moves")
{
u16 ability;
u32 species;
PARAMETRIZE { species = SPECIES_HITMONCHAN; ability = ABILITY_KEEN_EYE; }
PARAMETRIZE { species = SPECIES_STARYU; ability = ABILITY_ILLUMINATE; }
PARAMETRIZE { species = SPECIES_URSALUNA_BLOODMOON; ability = ABILITY_MINDS_EYE; }
PASSES_RANDOMLY(100, 100, RNG_ACCURACY);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(species) { Ability(ability); }
} WHEN {
TURN { MOVE(player, MOVE_SAND_ATTACK); MOVE(opponent, MOVE_TACKLE); }
} SCENE {
ABILITY_POPUP(opponent, ability);
NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
if (species == SPECIES_HITMONCHAN)
MESSAGE("Foe Hitmonchan's Keen Eye prevents accuracy loss!");
else if (species == SPECIES_STARYU)
MESSAGE("Foe Staryu's Illuminate prevents accuracy loss!");
else
MESSAGE("Foe Ursaluna's Mind's Eye prevents accuracy loss!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
}
}
SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye ignore target's evasion stat")
{
u16 ability;
u32 species;
PARAMETRIZE { species = SPECIES_HITMONCHAN; ability = ABILITY_KEEN_EYE; }
PARAMETRIZE { species = SPECIES_STARYU; ability = ABILITY_ILLUMINATE; }
PARAMETRIZE { species = SPECIES_URSALUNA_BLOODMOON; ability = ABILITY_MINDS_EYE; }
PASSES_RANDOMLY(100, 100, RNG_ACCURACY);
GIVEN {
ASSUME(gBattleMoves[MOVE_DOUBLE_TEAM].effect == EFFECT_EVASION_UP);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(species) { Ability(ability); }
} WHEN {
TURN { MOVE(player, MOVE_DOUBLE_TEAM); MOVE(opponent, MOVE_TACKLE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_TEAM, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
}
}
SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye are ignored by Mold Breaker abilities")
{
u16 abilityPlayer = ABILITY_NONE, abilityOpponent = ABILITY_NONE;
u16 speciesPlayer = SPECIES_NONE, speciesOpponent = SPECIES_NONE;
u32 j;
static const u16 moldBreakerAbilities[][2] = {
{SPECIES_PINSIR, ABILITY_MOLD_BREAKER},
{SPECIES_RESHIRAM, ABILITY_TURBOBLAZE},
{SPECIES_ZEKROM, ABILITY_TERAVOLT},
};
for (j = 0; j < ARRAY_COUNT(moldBreakerAbilities); j++) {
speciesPlayer = moldBreakerAbilities[j][0]; abilityPlayer = moldBreakerAbilities[j][1];
PARAMETRIZE { speciesOpponent = SPECIES_HITMONCHAN; abilityOpponent = ABILITY_KEEN_EYE; }
PARAMETRIZE { speciesOpponent = SPECIES_STARYU; abilityOpponent = ABILITY_ILLUMINATE; }
PARAMETRIZE { speciesOpponent = SPECIES_URSALUNA_BLOODMOON; abilityOpponent = ABILITY_MINDS_EYE; }
}
PASSES_RANDOMLY(gBattleMoves[MOVE_TACKLE].accuracy * 3 / 4, 100, RNG_ACCURACY);
GIVEN {
PLAYER(speciesPlayer) { Ability(abilityPlayer); }
OPPONENT(speciesOpponent) { Ability(abilityOpponent); }
} WHEN {
TURN { MOVE(player, MOVE_SAND_ATTACK); MOVE(opponent, MOVE_TACKLE); }
} SCENE {
ABILITY_POPUP(player, abilityPlayer);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SAND_ATTACK, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
}
}
SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye don't prevent Topsy-Turvy")
{
u16 ability;
u32 species;
PARAMETRIZE { species = SPECIES_HITMONCHAN; ability = ABILITY_KEEN_EYE; }
PARAMETRIZE { species = SPECIES_STARYU; ability = ABILITY_ILLUMINATE; }
PARAMETRIZE { species = SPECIES_URSALUNA_BLOODMOON; ability = ABILITY_MINDS_EYE; }
GIVEN {
ASSUME(gBattleMoves[MOVE_HONE_CLAWS].effect == EFFECT_ATTACK_ACCURACY_UP);
ASSUME(gBattleMoves[MOVE_TOPSY_TURVY].effect == EFFECT_TOPSY_TURVY);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(species) { Ability(ability); }
} WHEN {
TURN { MOVE(opponent, MOVE_HONE_CLAWS); MOVE(player, MOVE_TOPSY_TURVY); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_HONE_CLAWS, opponent);
if (species == SPECIES_HITMONCHAN) {
MESSAGE("Foe Hitmonchan's Attack rose!");
MESSAGE("Foe Hitmonchan's accuracy rose!");
} else if (species == SPECIES_STARYU) {
MESSAGE("Foe Staryu's Attack rose!");
MESSAGE("Foe Staryu's accuracy rose!");
} else {
MESSAGE("Foe Ursaluna's Attack rose!");
MESSAGE("Foe Ursaluna's accuracy rose!");
}
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOPSY_TURVY, player);
if (species == SPECIES_HITMONCHAN)
MESSAGE("Foe Hitmonchan's stat changes were all reversed!");
else if (species == SPECIES_STARYU)
MESSAGE("Foe Staryu's stat changes were all reversed!");
else
MESSAGE("Foe Ursaluna's stat changes were all reversed!");
} THEN {
EXPECT_EQ(opponent->statStages[STAT_ACC], DEFAULT_STAT_STAGE - 1);
}
}
SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye don't prevent receiving negative Attack stage changes from Baton Pass")
{
u16 ability;
u32 species;
PARAMETRIZE { species = SPECIES_HITMONCHAN; ability = ABILITY_KEEN_EYE; }
PARAMETRIZE { species = SPECIES_STARYU; ability = ABILITY_ILLUMINATE; }
PARAMETRIZE { species = SPECIES_URSALUNA_BLOODMOON; ability = ABILITY_MINDS_EYE; }
GIVEN {
ASSUME(gBattleMoves[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(species) { Ability(ability); }
} WHEN {
TURN { MOVE(player, MOVE_SAND_ATTACK);
MOVE(opponent, MOVE_BATON_PASS);
SEND_OUT(opponent, 1);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SAND_ATTACK, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, opponent);
if (species == SPECIES_HITMONCHAN)
MESSAGE("2 sent out Hitmonchan!");
else if (species == SPECIES_STARYU)
MESSAGE("2 sent out Staryu!");
else
MESSAGE("2 sent out Ursaluna!");
} THEN {
EXPECT_EQ(opponent->statStages[STAT_ACC], DEFAULT_STAT_STAGE - 1);
}
}
SINGLE_BATTLE_TEST("Keen Eye & Gen9+ Illuminate don't prevent Spectral Thief from resetting positive accuracy stage changes")
{
u16 ability;
u32 species;
PARAMETRIZE { species = SPECIES_HITMONCHAN; ability = ABILITY_KEEN_EYE; }
PARAMETRIZE { species = SPECIES_STARYU; ability = ABILITY_ILLUMINATE; }
GIVEN {
ASSUME(gBattleMoves[MOVE_HONE_CLAWS].effect == EFFECT_ATTACK_ACCURACY_UP);
ASSUME(gBattleMoves[MOVE_SPECTRAL_THIEF].effect == EFFECT_SPECTRAL_THIEF);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(species) { Ability(ability); }
} WHEN {
TURN { MOVE(opponent, MOVE_HONE_CLAWS); MOVE(player, MOVE_SPECTRAL_THIEF); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_HONE_CLAWS, opponent);
if (species == SPECIES_HITMONCHAN)
{
MESSAGE("Foe Hitmonchan's Attack rose!");
MESSAGE("Foe Hitmonchan's accuracy rose!");
}
else
{
MESSAGE("Foe Staryu's Attack rose!");
MESSAGE("Foe Staryu's accuracy rose!");
}
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPECTRAL_THIEF, player);
MESSAGE("Wobbuffet stole the target's boosted stats!");
} THEN {
EXPECT_EQ(opponent->statStages[STAT_ACC], DEFAULT_STAT_STAGE);
}
}

View file

@ -0,0 +1,71 @@
#include "global.h"
#include "test/battle.h"
SINGLE_BATTLE_TEST("Mind's Eye allows to hit Ghost-type Pokémon with Normal- and Fighting-type moves")
{
u32 move;
PARAMETRIZE { move = MOVE_TACKLE; }
PARAMETRIZE { move = MOVE_KARATE_CHOP; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_MINDS_EYE); };
OPPONENT(SPECIES_GASTLY);
} WHEN {
TURN { MOVE(player, move); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, move, player);
HP_BAR(opponent);
}
}
// No current official way to test this, effect based on Smogon's NatDex format.
SINGLE_BATTLE_TEST("Mind's Eye doesn't bypass a Ghost-type's Wonder Guard")
{
u32 move;
PARAMETRIZE { move = MOVE_TACKLE; }
PARAMETRIZE { move = MOVE_KARATE_CHOP; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SCRAPPY); };
OPPONENT(SPECIES_SHEDINJA) { Ability(ABILITY_WONDER_GUARD); };
} WHEN {
TURN { MOVE(player, move); }
} SCENE {
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, move, player);
HP_BAR(opponent);
}
ABILITY_POPUP(opponent, ABILITY_WONDER_GUARD);
MESSAGE("Foe Shedinja avoided damage with Wonder Guard!");
}
}
//// AI TESTS ////
AI_SINGLE_BATTLE_TEST("AI doesn't use accuracy-lowering moves if it knows that the foe has Mind's Eye")
{
u32 abilityAI = ABILITY_NONE, moveAI = MOVE_NONE, j = 0;
for (j = MOVE_NONE + 1; j < MOVES_COUNT; j++)
{
if (gBattleMoves[j].effect == EFFECT_ACCURACY_DOWN || gBattleMoves[j].effect == EFFECT_ACCURACY_DOWN_2) {
PARAMETRIZE{ moveAI = j; abilityAI = ABILITY_SWIFT_SWIM; }
PARAMETRIZE{ moveAI = j; abilityAI = ABILITY_MOLD_BREAKER; }
}
}
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_MINDS_EYE); }
OPPONENT(SPECIES_BASCULEGION) { Moves(MOVE_CELEBRATE, moveAI); Ability(abilityAI); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
TURN { MOVE(player, MOVE_TACKLE);
if (abilityAI == ABILITY_MOLD_BREAKER) { SCORE_GT(opponent, moveAI, MOVE_CELEBRATE); }
else { SCORE_EQ(opponent, moveAI, MOVE_CELEBRATE); }
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
if (abilityAI == ABILITY_MOLD_BREAKER) { ANIMATION(ANIM_TYPE_MOVE, moveAI, opponent); }
}
}