sovereignx/test/battle/ai/ai.c

809 lines
39 KiB
C
Raw Normal View History

#include "global.h"
#include "test/battle.h"
#include "battle_ai_util.h"
AI_SINGLE_BATTLE_TEST("AI prefers Bubble over Water Gun if it's slower")
{
u32 speedPlayer, speedAi;
PARAMETRIZE { speedPlayer = 200; speedAi = 10; }
PARAMETRIZE { speedPlayer = 10; speedAi = 200; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_SCIZOR) { Speed(speedPlayer); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_WATER_GUN, MOVE_BUBBLE); Speed(speedAi); }
} WHEN {
if (speedPlayer > speedAi)
{
TURN { SCORE_GT(opponent, MOVE_BUBBLE, MOVE_WATER_GUN); }
TURN { SCORE_GT(opponent, MOVE_BUBBLE, MOVE_WATER_GUN); }
}
else
{
TURN { SCORE_EQ(opponent, MOVE_BUBBLE, MOVE_WATER_GUN); }
TURN { SCORE_EQ(opponent, MOVE_BUBBLE, MOVE_WATER_GUN); }
}
}
}
AI_SINGLE_BATTLE_TEST("AI prefers Water Gun over Bubble if it knows that foe has Contrary")
{
u32 abilityAI;
PARAMETRIZE { abilityAI = ABILITY_MOXIE; }
PARAMETRIZE { abilityAI = ABILITY_MOLD_BREAKER; } // Mold Breaker ignores Contrary.
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_SHUCKLE) { Ability(ABILITY_CONTRARY); }
OPPONENT(SPECIES_PINSIR) { Moves(MOVE_WATER_GUN, MOVE_BUBBLE); Ability(abilityAI); }
} WHEN {
TURN { MOVE(player, MOVE_DEFENSE_CURL); }
TURN { MOVE(player, MOVE_DEFENSE_CURL);
if (abilityAI == ABILITY_MOLD_BREAKER) { SCORE_EQ(opponent, MOVE_WATER_GUN, MOVE_BUBBLE); }
else { SCORE_GT(opponent, MOVE_WATER_GUN, MOVE_BUBBLE); }}
} SCENE {
MESSAGE("Shuckle's Defense fell!"); // Contrary activates
} THEN {
EXPECT(gBattleResources->aiData->abilities[B_POSITION_PLAYER_LEFT] == ABILITY_CONTRARY);
}
}
AI_SINGLE_BATTLE_TEST("AI prefers moves with better accuracy, but only if they both require the same number of hits to ko")
{
u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE;
u16 hp, expectedMove, turns, abilityAtk, expectedMove2;
abilityAtk = ABILITY_NONE;
expectedMove2 = MOVE_NONE;
// Here it's a simple test, both Slam and Strength deal the same damage, but Strength always hits, whereas Slam often misses.
PARAMETRIZE { move1 = MOVE_SLAM; move2 = MOVE_STRENGTH; move3 = MOVE_TACKLE; hp = 490; expectedMove = MOVE_STRENGTH; turns = 4; }
PARAMETRIZE { move1 = MOVE_SLAM; move2 = MOVE_STRENGTH; move3 = MOVE_SWIFT; move4 = MOVE_TACKLE; hp = 365; expectedMove = MOVE_STRENGTH; turns = 3; }
PARAMETRIZE { move1 = MOVE_SLAM; move2 = MOVE_STRENGTH; move3 = MOVE_SWIFT; move4 = MOVE_TACKLE; hp = 245; expectedMove = MOVE_STRENGTH; turns = 2; }
PARAMETRIZE { move1 = MOVE_SLAM; move2 = MOVE_STRENGTH; move3 = MOVE_SWIFT; move4 = MOVE_TACKLE; hp = 125; expectedMove = MOVE_STRENGTH; turns = 1; }
// Mega Kick deals more damage, but can miss more often. Here, AI should choose Mega Kick if it can faint target in less number of turns than Strength. Otherwise, it should use Strength.
PARAMETRIZE { move1 = MOVE_MEGA_KICK; move2 = MOVE_STRENGTH; move3 = MOVE_SWIFT; move4 = MOVE_TACKLE; hp = 170; expectedMove = MOVE_MEGA_KICK; turns = 1; }
PARAMETRIZE { move1 = MOVE_MEGA_KICK; move2 = MOVE_STRENGTH; move3 = MOVE_SWIFT; move4 = MOVE_TACKLE; hp = 245; expectedMove = MOVE_STRENGTH; turns = 2; }
// Swift always hits and Guts has accuracy of 100%. Hustle lowers accuracy of all physical moves.
PARAMETRIZE { abilityAtk = ABILITY_HUSTLE; move1 = MOVE_MEGA_KICK; move2 = MOVE_STRENGTH; move3 = MOVE_SWIFT; move4 = MOVE_TACKLE; hp = 5; expectedMove = MOVE_SWIFT; turns = 1; }
PARAMETRIZE { abilityAtk = ABILITY_HUSTLE; move1 = MOVE_MEGA_KICK; move2 = MOVE_STRENGTH; move3 = MOVE_GUST; move4 = MOVE_TACKLE; hp = 5; expectedMove = MOVE_GUST; turns = 1; }
// Mega Kick and Slam both have lower accuracy. Gust and Tackle both have 100, so AI can choose either of them.
PARAMETRIZE { move1 = MOVE_MEGA_KICK; move2 = MOVE_SLAM; move3 = MOVE_TACKLE; move4 = MOVE_GUST; hp = 5; expectedMove = MOVE_GUST; expectedMove2 = MOVE_TACKLE; turns = 1; }
// All moves hit with No guard ability
PARAMETRIZE { move1 = MOVE_MEGA_KICK; move2 = MOVE_GUST; hp = 5; expectedMove = MOVE_MEGA_KICK; expectedMove2 = MOVE_GUST; turns = 1; }
// Tests to compare move that always hits and a beneficial effect. A move with higher acc should be chosen in this case.
PARAMETRIZE { move1 = MOVE_SHOCK_WAVE; move2 = MOVE_ICY_WIND; hp = 5; expectedMove = MOVE_SHOCK_WAVE; turns = 1; }
PARAMETRIZE { move1 = MOVE_SHOCK_WAVE; move2 = MOVE_ICY_WIND; move3 = MOVE_THUNDERBOLT; hp = 5; expectedMove = MOVE_SHOCK_WAVE; expectedMove2 = MOVE_THUNDERBOLT; turns = 1; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { HP(hp); }
PLAYER(SPECIES_WOBBUFFET);
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
ASSUME(gMovesInfo[MOVE_SWIFT].accuracy == 0);
ASSUME(gMovesInfo[MOVE_SLAM].power == gMovesInfo[MOVE_STRENGTH].power);
ASSUME(gMovesInfo[MOVE_MEGA_KICK].power > gMovesInfo[MOVE_STRENGTH].power);
ASSUME(gMovesInfo[MOVE_SLAM].accuracy < gMovesInfo[MOVE_STRENGTH].accuracy);
ASSUME(gMovesInfo[MOVE_MEGA_KICK].accuracy < gMovesInfo[MOVE_STRENGTH].accuracy);
ASSUME(gMovesInfo[MOVE_TACKLE].accuracy == 100);
ASSUME(gMovesInfo[MOVE_GUST].accuracy == 100);
ASSUME(gMovesInfo[MOVE_SHOCK_WAVE].accuracy == 0);
ASSUME(gMovesInfo[MOVE_THUNDERBOLT].accuracy == 100);
ASSUME(gMovesInfo[MOVE_ICY_WIND].accuracy != 100);
ASSUME(gMovesInfo[MOVE_SLAM].category == DAMAGE_CATEGORY_PHYSICAL);
ASSUME(gMovesInfo[MOVE_STRENGTH].category == DAMAGE_CATEGORY_PHYSICAL);
ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL);
ASSUME(gMovesInfo[MOVE_MEGA_KICK].category == DAMAGE_CATEGORY_PHYSICAL);
ASSUME(gMovesInfo[MOVE_SWIFT].category == DAMAGE_CATEGORY_SPECIAL);
ASSUME(gMovesInfo[MOVE_SHOCK_WAVE].category == DAMAGE_CATEGORY_SPECIAL);
ASSUME(gMovesInfo[MOVE_ICY_WIND].category == DAMAGE_CATEGORY_SPECIAL);
ASSUME(gMovesInfo[MOVE_THUNDERBOLT].category == DAMAGE_CATEGORY_SPECIAL);
ASSUME(gMovesInfo[MOVE_GUST].category == DAMAGE_CATEGORY_SPECIAL);
OPPONENT(SPECIES_EXPLOUD) { Moves(move1, move2, move3, move4); Ability(abilityAtk); SpAttack(50); } // Low Sp.Atk, so Swift deals less damage than Strength.
} WHEN {
switch (turns)
{
case 1:
if (expectedMove2 != MOVE_NONE) {
TURN { EXPECT_MOVES(opponent, expectedMove, expectedMove2); SEND_OUT(player, 1); }
}
else {
TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); }
}
break;
case 2:
TURN { EXPECT_MOVE(opponent, expectedMove); }
TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); }
break;
case 3:
TURN { EXPECT_MOVE(opponent, expectedMove); }
TURN { EXPECT_MOVE(opponent, expectedMove); }
TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); }
break;
case 4:
TURN { EXPECT_MOVE(opponent, expectedMove); }
TURN { EXPECT_MOVE(opponent, expectedMove); }
TURN { EXPECT_MOVE(opponent, expectedMove); }
TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); }
break;
}
} SCENE {
MESSAGE("Wobbuffet fainted!");
}
}
AI_SINGLE_BATTLE_TEST("AI prefers moves which deal more damage instead of moves which are super-effective but deal less damage")
{
u8 turns = 0;
u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE;
u16 expectedMove, abilityAtk, abilityDef;
abilityAtk = ABILITY_NONE;
// Scald and Poison Jab take 3 hits, Waterfall takes 2.
PARAMETRIZE { move1 = MOVE_WATERFALL; move2 = MOVE_SCALD; move3 = MOVE_POISON_JAB; move4 = MOVE_WATER_GUN; expectedMove = MOVE_WATERFALL; turns = 2; }
// Poison Jab takes 3 hits, Water gun 5. Immunity so there's no poison chip damage.
PARAMETRIZE { move1 = MOVE_POISON_JAB; move2 = MOVE_WATER_GUN; expectedMove = MOVE_POISON_JAB; abilityDef = ABILITY_IMMUNITY; turns = 3; }
GIVEN {
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
ASSUME(gMovesInfo[MOVE_WATERFALL].category == DAMAGE_CATEGORY_PHYSICAL);
ASSUME(gMovesInfo[MOVE_SCALD].category == DAMAGE_CATEGORY_SPECIAL);
ASSUME(gMovesInfo[MOVE_POISON_JAB].category == DAMAGE_CATEGORY_PHYSICAL);
ASSUME(gMovesInfo[MOVE_WATER_GUN].category == DAMAGE_CATEGORY_SPECIAL);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_TYPHLOSION) { Ability(abilityDef); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_NIDOQUEEN) { Moves(move1, move2, move3, move4); Ability(abilityAtk); }
} WHEN {
switch (turns)
{
case 2:
TURN { EXPECT_MOVE(opponent, expectedMove); }
TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); }
break;
case 3:
TURN { EXPECT_MOVE(opponent, expectedMove); }
TURN { EXPECT_MOVE(opponent, expectedMove); }
TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); }
break;
}
} SCENE {
MESSAGE("Typhlosion fainted!");
}
}
AI_SINGLE_BATTLE_TEST("AI prefers Earthquake over Drill Run if both require the same number of hits to ko")
{
// Drill Run has less accuracy than E-quake, but can score a higher crit. However the chance is too small, so AI should ignore it.
GIVEN {
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
ASSUME(gMovesInfo[MOVE_EARTHQUAKE].category == DAMAGE_CATEGORY_PHYSICAL); // Added because Geodude has to KO Typhlosion
ASSUME(gMovesInfo[MOVE_DRILL_RUN].category == DAMAGE_CATEGORY_PHYSICAL); // Added because Geodude has to KO Typhlosion
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_TYPHLOSION);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_GEODUDE) { Moves(MOVE_EARTHQUAKE, MOVE_DRILL_RUN); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_EARTHQUAKE); }
TURN { EXPECT_MOVE(opponent, MOVE_EARTHQUAKE); SEND_OUT(player, 1); }
}
SCENE {
MESSAGE("Typhlosion fainted!");
}
}
AI_SINGLE_BATTLE_TEST("AI prefers a weaker move over a one with a downside effect if both require the same number of hits to ko")
{
u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE;
u16 hp, expectedMove, turns;
// Both moves require the same number of turns but Flamethrower will be chosen over Overheat (powerful effect)
PARAMETRIZE { move1 = MOVE_OVERHEAT; move2 = MOVE_FLAMETHROWER; hp = 300; expectedMove = MOVE_FLAMETHROWER; turns = 2; }
// Overheat kill in least amount of turns
PARAMETRIZE { move1 = MOVE_OVERHEAT; move2 = MOVE_FLAMETHROWER; hp = 250; expectedMove = MOVE_OVERHEAT; turns = 1; }
GIVEN {
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
ASSUME(gMovesInfo[MOVE_FLAMETHROWER].category == DAMAGE_CATEGORY_SPECIAL); // Added because Typhlosion has to KO Wobbuffet
ASSUME(gMovesInfo[MOVE_OVERHEAT].category == DAMAGE_CATEGORY_SPECIAL); // Added because Typhlosion has to KO Wobbuffet
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { HP(hp); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_TYPHLOSION) { Moves(move1, move2, move3, move4); }
} WHEN {
switch (turns)
{
case 1:
TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); }
break;
case 2:
TURN { EXPECT_MOVE(opponent, expectedMove); }
TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); }
break;
}
} SCENE {
MESSAGE("Wobbuffet fainted!");
}
}
AI_SINGLE_BATTLE_TEST("AI prefers moves with the best possible score, chosen randomly if tied")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { HP(5); };
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_THUNDERBOLT, MOVE_SLUDGE_BOMB, MOVE_TAKE_DOWN); }
} WHEN {
TURN { EXPECT_MOVES(opponent, MOVE_THUNDERBOLT, MOVE_SLUDGE_BOMB); SEND_OUT(player, 1); }
}
SCENE {
MESSAGE("Wobbuffet fainted!");
}
}
AI_SINGLE_BATTLE_TEST("AI can choose a status move that boosts the attack by two")
{
GIVEN {
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
ASSUME(gMovesInfo[MOVE_STRENGTH].category == DAMAGE_CATEGORY_PHYSICAL);
ASSUME(gMovesInfo[MOVE_HORN_ATTACK].category == DAMAGE_CATEGORY_PHYSICAL);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { HP(277); };
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_KANGASKHAN) { Moves(MOVE_STRENGTH, MOVE_HORN_ATTACK, MOVE_SWORDS_DANCE); }
} WHEN {
TURN { EXPECT_MOVES(opponent, MOVE_STRENGTH, MOVE_SWORDS_DANCE); }
TURN { EXPECT_MOVE(opponent, MOVE_STRENGTH); SEND_OUT(player, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI chooses the safest option to faint the target, taking into account accuracy and move effect")
{
u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE;
u16 expectedMove, expectedMove2 = MOVE_NONE;
u16 abilityAtk = ABILITY_NONE, holdItemAtk = ITEM_NONE;
// Psychic is not very effective, but always hits. Solarbeam requires a charging turn, Double Edge has recoil and Focus Blast can miss;
PARAMETRIZE { abilityAtk = ABILITY_STURDY; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE; expectedMove = MOVE_PSYCHIC; }
// Same as above, but ai mon has rock head ability, so it can use Double Edge without taking recoil damage. Psychic can also lower Special Defense,
// but because it faints the target it doesn't matter.
PARAMETRIZE { abilityAtk = ABILITY_ROCK_HEAD; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE;
expectedMove = MOVE_PSYCHIC; expectedMove2 = MOVE_DOUBLE_EDGE; }
// This time it's Solarbeam + Psychic, because the weather is sunny.
PARAMETRIZE { abilityAtk = ABILITY_DROUGHT; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE;
expectedMove = MOVE_PSYCHIC; expectedMove2 = MOVE_SOLAR_BEAM; }
// Psychic and Solar Beam are chosen because user is holding Power Herb
PARAMETRIZE { abilityAtk = ABILITY_STURDY; holdItemAtk = ITEM_POWER_HERB; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE;
expectedMove = MOVE_PSYCHIC; expectedMove2 = MOVE_SOLAR_BEAM; }
// Skull Bash is chosen because it's the most accurate and is holding Power Herb
PARAMETRIZE { abilityAtk = ABILITY_STURDY; holdItemAtk = ITEM_POWER_HERB; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SKULL_BASH; move3 = MOVE_SLAM; move4 = MOVE_DOUBLE_EDGE;
expectedMove = MOVE_SKULL_BASH; }
Secondary effects overhaul minor follow-up (#4062) * settwoturnstring command * Unified two-turn attacks and Meteor Beam To do: Solar Beam * Solar Beam Also fixed various function, removed EFFECT_GUST (who knows why that exists?) * Updated Solar Beam + tests * Redid two turn move + animations logic Removed pointless various function; to do: remove Skull Bash, fix AI test * Removed now-pointless flag * Removed Skull Bash And temporarily commented out failing AI tests * Removed Sky Uppercut effect Not sure when or why this was ever necessary * Removed BattleScript_EffectSemiInvulnerable Now uses BattleScript_EffectTwoTurnsAttack. Kept the effect; used the argument field to determine which STATUS3 such moves should apply but added a function to jump over weather checks in BattleScript_EffectTwoTurnsAttack if the current move is semi-invulnerable (since the instant-fire weather check and STATUS3 use the same field) * Applied review changes * Replaced VARIOUS with callnative Tried to fix test but couldn't :/ wtf is going on * Fixed one AI test Cant fix the other... * Added KNOWN_FAILING to failing AI tests Separated them out into their own test * Optimised script, overhauled charge turn string setting Condensed multiple confusing macros into one, jumpifweathercheckchargeeffects. Script now tweaked and trimmed, string ids for charge turns now added to argument along with status3 (thanks to compression macro) and instant-fire-weather for semi-invulnerable and two-turn moves respectively. Also introduced a savedStringId in gBattleScripting to make string selection work. * Unified two turn move tests + minor corrections * Added semi-invulnerable move tests Set the Razor Wind test to known failing - something to do with its animation? --------- Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
2024-02-04 22:02:59 +00:00
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { HP(5); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_GEODUDE) { Moves(move1, move2, move3, move4); Ability(abilityAtk); Item(holdItemAtk); }
} WHEN {
TURN { if (expectedMove2 == MOVE_NONE) { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); }
else {EXPECT_MOVES(opponent, expectedMove, expectedMove2); SCORE_EQ(opponent, expectedMove, expectedMove2); SEND_OUT(player, 1);}
}
}
SCENE {
MESSAGE("Wobbuffet fainted!");
}
}
AI_SINGLE_BATTLE_TEST("AI chooses the safest option to faint the target, taking into account accuracy and move effect failing")
{
u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE;
u16 expectedMove, expectedMove2 = MOVE_NONE;
u16 abilityAtk = ABILITY_NONE, holdItemAtk = ITEM_NONE;
// Fiery Dance and Skull Bash are chosen because user is holding Power Herb
PARAMETRIZE { abilityAtk = ABILITY_STURDY; holdItemAtk = ITEM_POWER_HERB; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SKULL_BASH; move3 = MOVE_FIERY_DANCE; move4 = MOVE_DOUBLE_EDGE;
expectedMove = MOVE_FIERY_DANCE; expectedMove2 = MOVE_SKULL_BASH; }
// Crabhammer is chosen even if Skull Bash is more accurate, the user has no Power Herb
PARAMETRIZE { abilityAtk = ABILITY_STURDY; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SKULL_BASH; move3 = MOVE_SLAM; move4 = MOVE_CRABHAMMER;
expectedMove = MOVE_CRABHAMMER; }
Secondary effects overhaul minor follow-up (#4062) * settwoturnstring command * Unified two-turn attacks and Meteor Beam To do: Solar Beam * Solar Beam Also fixed various function, removed EFFECT_GUST (who knows why that exists?) * Updated Solar Beam + tests * Redid two turn move + animations logic Removed pointless various function; to do: remove Skull Bash, fix AI test * Removed now-pointless flag * Removed Skull Bash And temporarily commented out failing AI tests * Removed Sky Uppercut effect Not sure when or why this was ever necessary * Removed BattleScript_EffectSemiInvulnerable Now uses BattleScript_EffectTwoTurnsAttack. Kept the effect; used the argument field to determine which STATUS3 such moves should apply but added a function to jump over weather checks in BattleScript_EffectTwoTurnsAttack if the current move is semi-invulnerable (since the instant-fire weather check and STATUS3 use the same field) * Applied review changes * Replaced VARIOUS with callnative Tried to fix test but couldn't :/ wtf is going on * Fixed one AI test Cant fix the other... * Added KNOWN_FAILING to failing AI tests Separated them out into their own test * Optimised script, overhauled charge turn string setting Condensed multiple confusing macros into one, jumpifweathercheckchargeeffects. Script now tweaked and trimmed, string ids for charge turns now added to argument along with status3 (thanks to compression macro) and instant-fire-weather for semi-invulnerable and two-turn moves respectively. Also introduced a savedStringId in gBattleScripting to make string selection work. * Unified two turn move tests + minor corrections * Added semi-invulnerable move tests Set the Razor Wind test to known failing - something to do with its animation? --------- Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
2024-02-04 22:02:59 +00:00
KNOWN_FAILING;
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { HP(5); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_GEODUDE) { Moves(move1, move2, move3, move4); Ability(abilityAtk); Item(holdItemAtk); }
} WHEN {
TURN { if (expectedMove2 == MOVE_NONE) { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); }
else {EXPECT_MOVES(opponent, expectedMove, expectedMove2); SCORE_EQ(opponent, expectedMove, expectedMove2); SEND_OUT(player, 1);}
}
}
SCENE {
MESSAGE("Wobbuffet fainted!");
}
}
AI_SINGLE_BATTLE_TEST("AI won't use Solar Beam if there is no Sun up or the user is not holding Power Herb")
{
u16 abilityAtk = ABILITY_NONE;
u16 holdItemAtk = ITEM_NONE;
PARAMETRIZE { abilityAtk = ABILITY_DROUGHT; }
PARAMETRIZE { holdItemAtk = ITEM_POWER_HERB; }
PARAMETRIZE { }
GIVEN {
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
ASSUME(gMovesInfo[MOVE_SOLAR_BEAM].category == DAMAGE_CATEGORY_SPECIAL);
ASSUME(gMovesInfo[MOVE_GRASS_PLEDGE].category == DAMAGE_CATEGORY_SPECIAL);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { HP(211); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_TYPHLOSION) { Moves(MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); Ability(abilityAtk); Item(holdItemAtk); }
} WHEN {
if (abilityAtk == ABILITY_DROUGHT) {
TURN { EXPECT_MOVES(opponent, MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); }
TURN { EXPECT_MOVES(opponent, MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); SEND_OUT(player, 1); }
} else if (holdItemAtk == ITEM_POWER_HERB) {
TURN { EXPECT_MOVES(opponent, MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); MOVE(player, MOVE_KNOCK_OFF); }
TURN { EXPECT_MOVE(opponent, MOVE_GRASS_PLEDGE); SEND_OUT(player, 1); }
} else {
TURN { EXPECT_MOVE(opponent, MOVE_GRASS_PLEDGE); }
TURN { EXPECT_MOVE(opponent, MOVE_GRASS_PLEDGE); SEND_OUT(player, 1); }
}
} SCENE {
MESSAGE("Wobbuffet fainted!");
}
}
AI_SINGLE_BATTLE_TEST("AI won't use ground type attacks against flying type Pokemon unless Gravity is in effect")
{
GIVEN {
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
ASSUME(gMovesInfo[MOVE_EARTHQUAKE].category == DAMAGE_CATEGORY_PHYSICAL); // Otherwise, it doesn't KO Crobat
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_CROBAT);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_NIDOQUEEN) { Moves(MOVE_EARTHQUAKE, MOVE_TACKLE, MOVE_POISON_STING, MOVE_GUST); }
} WHEN {
TURN { NOT_EXPECT_MOVE(opponent, MOVE_EARTHQUAKE); }
TURN { MOVE(player, MOVE_GRAVITY); NOT_EXPECT_MOVE(opponent, MOVE_EARTHQUAKE); }
TURN { EXPECT_MOVE(opponent, MOVE_EARTHQUAKE); SEND_OUT(player, 1); }
} SCENE {
MESSAGE("Gravity intensified!");
}
}
AI_DOUBLE_BATTLE_TEST("AI won't use a Weather changing move if partner already chose such move")
{
u32 j, k;
static const u16 weatherMoves[] = {MOVE_SUNNY_DAY, MOVE_HAIL, MOVE_RAIN_DANCE, MOVE_SANDSTORM, MOVE_SNOWSCAPE};
u16 weatherMoveLeft = MOVE_NONE, weatherMoveRight = MOVE_NONE;
for (j = 0; j < ARRAY_COUNT(weatherMoves); j++)
{
for (k = 0; k < ARRAY_COUNT(weatherMoves); k++)
{
PARAMETRIZE { weatherMoveLeft = weatherMoves[j]; weatherMoveRight = weatherMoves[k]; }
}
}
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(weatherMoveLeft); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, weatherMoveRight); }
} WHEN {
TURN { NOT_EXPECT_MOVE(opponentRight, weatherMoveRight);
SCORE_LT_VAL(opponentRight, weatherMoveRight, AI_SCORE_DEFAULT, target:playerLeft);
SCORE_LT_VAL(opponentRight, weatherMoveRight, AI_SCORE_DEFAULT, target:playerRight);
SCORE_LT_VAL(opponentRight, weatherMoveRight, AI_SCORE_DEFAULT, target:opponentLeft);
}
}
}
AI_DOUBLE_BATTLE_TEST("AI will not use Helping Hand if partner does not have any damage moves")
{
u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE;
PARAMETRIZE { move1 = MOVE_LEER; move2 = MOVE_TOXIC; }
PARAMETRIZE { move1 = MOVE_HELPING_HAND; move2 = MOVE_PROTECT; }
PARAMETRIZE { move1 = MOVE_ACUPRESSURE; move2 = MOVE_DOUBLE_TEAM; move3 = MOVE_TOXIC; move4 = MOVE_PROTECT; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HELPING_HAND, MOVE_TACKLE); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(move1, move2, move3, move4); }
} WHEN {
TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_HELPING_HAND);
SCORE_LT_VAL(opponentLeft, MOVE_HELPING_HAND, AI_SCORE_DEFAULT, target:playerLeft);
SCORE_LT_VAL(opponentLeft, MOVE_HELPING_HAND, AI_SCORE_DEFAULT, target:playerRight);
SCORE_LT_VAL(opponentLeft, MOVE_HELPING_HAND, AI_SCORE_DEFAULT, target:opponentLeft);
}
} SCENE {
NOT MESSAGE("The opposing Wobbuffet used Helping Hand!");
}
}
AI_DOUBLE_BATTLE_TEST("AI will not use a status move if partner already chose Helping Hand")
{
s32 j;
u32 statusMove = MOVE_NONE;
for (j = MOVE_NONE + 1; j < MOVES_COUNT; j++)
{
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
if (gMovesInfo[j].category == DAMAGE_CATEGORY_STATUS) {
PARAMETRIZE { statusMove = j; }
}
}
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HELPING_HAND); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, statusMove); }
} WHEN {
TURN { NOT_EXPECT_MOVE(opponentRight, statusMove);
SCORE_LT_VAL(opponentRight, statusMove, AI_SCORE_DEFAULT, target:playerLeft);
SCORE_LT_VAL(opponentRight, statusMove, AI_SCORE_DEFAULT, target:playerRight);
SCORE_LT_VAL(opponentRight, statusMove, AI_SCORE_DEFAULT, target:opponentLeft);
}
} SCENE {
MESSAGE("The opposing Wobbuffet used Helping Hand!");
}
}
AI_SINGLE_BATTLE_TEST("AI without any flags chooses moves at random - singles")
{
GIVEN {
AI_FLAGS(0);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_NIDOQUEEN) { Moves(MOVE_SPLASH, MOVE_EXPLOSION, MOVE_RAGE, MOVE_HELPING_HAND); }
} WHEN {
TURN { EXPECT_MOVES(opponent, MOVE_SPLASH, MOVE_EXPLOSION, MOVE_RAGE, MOVE_HELPING_HAND);
SCORE_EQ_VAL(opponent, MOVE_SPLASH, AI_SCORE_DEFAULT);
SCORE_EQ_VAL(opponent, MOVE_EXPLOSION, AI_SCORE_DEFAULT);
SCORE_EQ_VAL(opponent, MOVE_RAGE, AI_SCORE_DEFAULT);
SCORE_EQ_VAL(opponent, MOVE_HELPING_HAND, AI_SCORE_DEFAULT);
}
}
}
AI_DOUBLE_BATTLE_TEST("AI without any flags chooses moves at random - doubles")
{
GIVEN {
AI_FLAGS(0);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_NIDOQUEEN) { Moves(MOVE_SPLASH, MOVE_EXPLOSION, MOVE_RAGE, MOVE_HELPING_HAND); }
OPPONENT(SPECIES_NIDOQUEEN) { Moves(MOVE_SPLASH, MOVE_EXPLOSION, MOVE_RAGE, MOVE_HELPING_HAND); }
} WHEN {
TURN { EXPECT_MOVES(opponentLeft, MOVE_SPLASH, MOVE_EXPLOSION, MOVE_RAGE, MOVE_HELPING_HAND);
EXPECT_MOVES(opponentRight, MOVE_SPLASH, MOVE_EXPLOSION, MOVE_RAGE, MOVE_HELPING_HAND);
SCORE_EQ_VAL(opponentLeft, MOVE_SPLASH, AI_SCORE_DEFAULT, target:playerLeft);
SCORE_EQ_VAL(opponentLeft, MOVE_EXPLOSION, AI_SCORE_DEFAULT, target:playerLeft);
SCORE_EQ_VAL(opponentLeft, MOVE_RAGE, AI_SCORE_DEFAULT, target:playerLeft);
SCORE_EQ_VAL(opponentLeft, MOVE_HELPING_HAND, AI_SCORE_DEFAULT, target:playerLeft);
SCORE_EQ_VAL(opponentRight, MOVE_SPLASH, AI_SCORE_DEFAULT, target:playerLeft);
SCORE_EQ_VAL(opponentRight, MOVE_EXPLOSION, AI_SCORE_DEFAULT, target:playerLeft);
SCORE_EQ_VAL(opponentRight, MOVE_RAGE, AI_SCORE_DEFAULT, target:playerLeft);
SCORE_EQ_VAL(opponentRight, MOVE_HELPING_HAND, AI_SCORE_DEFAULT, target:playerLeft);
}
}
}
Smarter SwitchAI Mon Choices | HasBadOdds Switch Check (#3253) * SwitchAI makes much smarter mon choices * Add HasHadOdds check to ShouldSwitch decision * Remove early return * Rework Baton Pass check as per discussion with Alex * Forgot to adjust a comment * Don't program before breakfast lol (if / else if fix) * Switch AI_CalcDamage for AI_DATA->simulatedDmg in HasBadOdds Thanks Alex! :D * Typo in a hitToKO comparison * Remove and replace AI_CalcPartyMonBestMoveDamage and IsAiPartyMonOHKOBy from https://github.com/rh-hideout/pokeemerald-expansion/pull/3146 See https://discord.com/channels/419213663107416084/1144447521960251472 for details * Major refactor, new struct, switchin considers damage / healing from hazards / status / held item / weather * Forgot Snow exists and heals Ice Body, haven't played Switch games lol * (https://github.com/rh-hideout/pokeemerald-expansion/commit/766a1a27a7298e50dd89c5fecc1989b3dd8b8ce3) Compatibility, use new struct field instead of function call * Fixing oversight from previous upstream merge * Improve TSpikes handling to make GetSwitchinHazardDamage more applicable Small fixes: - EFFECT_EXPLOSION typo (!= to ==) - Order of if statements near bestResistEffective - Spacing of terms in big HasBadOdds if statements * Forgot to uncomment blocks disabled for debugging what turned out to be vanilla behaviour lol * Remove another holdover from debugging, sorry :/ * Lastly, undoing my debug trainer * Type matchup based on species type rather than current type Suggested by BLourenco on Discord, the idea is that a mon that's had its type affected by a move like Soak will still have moves as though it was its regular typing, and so prioritizing the temporary typing wouldn't be ideal. https://discord.com/channels/419213663107416084/1144447521960251472/1146644578141736970 * gActiveBattler upcoming merge fixes * Egg changes part 1 * Egg changes part 2, just need to address EWRAM still * Move SwitchinCandidate struct to AiLogicData * Consider Steel type when checking TSpikes * Comment about CanBePoisoned compatibility * Changes for Egg's 2nd review * Put period back in comment, whoops lol * Latest upcoming merge fixes * Missed a few u32 updates * Combine GetBestMonIntegrate functions / flags, some modularization * Fix merge error * Make modern fixes * Two tests done, two to go * Accidentally pushed reference test, removing it * Type matchup switching tests * Tests for defensive vs offense switches --------- Co-authored-by: DizzyEggg <jajkodizzy@wp.pl>
2023-11-11 13:37:35 +00:00
AI_SINGLE_BATTLE_TEST("AI will choose either Rock Tomb or Bulldoze if Stat drop effect will activate and they kill with the same number of hits")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { HP(46); Speed(20); }
PLAYER(SPECIES_WYNAUT) { Speed(20); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(10); Moves(MOVE_BULLDOZE, MOVE_ROCK_TOMB); }
} WHEN {
TURN { EXPECT_MOVES(opponent, MOVE_BULLDOZE, MOVE_ROCK_TOMB); }
TURN { EXPECT_MOVES(opponent, MOVE_BULLDOZE, MOVE_ROCK_TOMB); SEND_OUT(player, 1); }
} SCENE {
MESSAGE("Wobbuffet fainted!");
}
}
AI_SINGLE_BATTLE_TEST("First Impression is preferred on the first turn of the species if it's the best dmg move")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_FIRST_IMPRESSION].effect == EFFECT_FIRST_TURN_ONLY);
ASSUME(gMovesInfo[MOVE_FIRST_IMPRESSION].power == 90);
ASSUME(gMovesInfo[MOVE_LUNGE].power == 80);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_KANGASKHAN);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_FIRST_IMPRESSION, MOVE_LUNGE); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_FIRST_IMPRESSION); }
TURN { EXPECT_MOVE(opponent, MOVE_LUNGE); }
}
}
AI_SINGLE_BATTLE_TEST("First Impression is not chosen if it's blocked by certain abilities")
{
u16 species;
u16 ability;
PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; }
PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; }
PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; }
GIVEN {
ASSUME(gMovesInfo[MOVE_FIRST_IMPRESSION].effect == EFFECT_FIRST_TURN_ONLY);
ASSUME(gMovesInfo[MOVE_FIRST_IMPRESSION].power == 90);
ASSUME(gMovesInfo[MOVE_LUNGE].power == 80);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
PLAYER(species) { Ability(ability); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_FIRST_IMPRESSION, MOVE_LUNGE); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_LUNGE); }
}
}
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 only choose Surf 1/3 times if the opposing mon has Volt Absorb")
{
PASSES_RANDOMLY(1, 3, RNG_AI_ABILITY);
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); }
TURN { EXPECT_MOVE(opponent, MOVE_SURF); }
} SCENE {
MESSAGE("The opposing Lanturn used Surf!");
MESSAGE("The opposing Lanturn used Surf!");
}
}
AI_SINGLE_BATTLE_TEST("AI will choose Thunderbolt then Surf 2/3 times if the opposing mon has Volt Absorb")
{
PASSES_RANDOMLY(2, 3, RNG_AI_ABILITY);
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_THUNDERBOLT); }
TURN { EXPECT_MOVE(opponent, MOVE_SURF); }
} SCENE {
MESSAGE("The opposing Lanturn used Thunderbolt!");
MESSAGE("The opposing Lanturn used 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);
}
}
}
AI_DOUBLE_BATTLE_TEST("AI will not choose Earthquake if it damages the partner")
{
u32 species;
PARAMETRIZE { species = SPECIES_CHARIZARD; }
PARAMETRIZE { species = SPECIES_CHARMANDER; }
PARAMETRIZE { species = SPECIES_CHIKORITA; }
GIVEN {
ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_PHANPY) { Moves(MOVE_EARTHQUAKE, MOVE_TACKLE); }
OPPONENT(species) { Moves(MOVE_CELEBRATE); }
} WHEN {
if (species == SPECIES_CHARIZARD)
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
else
TURN { EXPECT_MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); }
}
}
AI_DOUBLE_BATTLE_TEST("AI will choose Earthquake if partner is not alive")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_EARTHQUAKE, MOVE_TACKLE); }
OPPONENT(SPECIES_PIKACHU) { HP(1); Moves(MOVE_CELEBRATE); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentRight); }
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
}
}
AI_DOUBLE_BATTLE_TEST("AI will choose Earthquake if it kill an opposing mon and does 1/3 of damage to AI")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_EARTHQUAKE, MOVE_TACKLE); }
OPPONENT(SPECIES_PARAS) { Moves(MOVE_CELEBRATE); }
} WHEN {
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
}
}
AI_DOUBLE_BATTLE_TEST("AI will the see a corresponding absorbing ability on partner to one of its moves")
{
u32 ability;
PARAMETRIZE { ability = ABILITY_LIGHTNING_ROD; }
PARAMETRIZE { ability = ABILITY_STATIC; }
GIVEN {
ASSUME(gMovesInfo[MOVE_DISCHARGE].target == MOVE_TARGET_FOES_AND_ALLY);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_DISCHARGE, MOVE_TACKLE); }
OPPONENT(SPECIES_PIKACHU) { HP(1); Ability(ability); Moves(MOVE_CELEBRATE); }
} WHEN {
if (ability == ABILITY_LIGHTNING_ROD)
TURN { EXPECT_MOVE(opponentLeft, MOVE_DISCHARGE); }
else
TURN { EXPECT_MOVE(opponentLeft, MOVE_TACKLE); }
}
}
2024-06-09 21:41:12 +01:00
AI_SINGLE_BATTLE_TEST("AI calculates guaranteed criticals and detects critical immunity")
{
u32 ability;
PARAMETRIZE { ability = ABILITY_SWIFT_SWIM; }
PARAMETRIZE { ability = ABILITY_SHELL_ARMOR; }
GIVEN {
ASSUME(gMovesInfo[MOVE_STORM_THROW].alwaysCriticalHit);
ASSUME(gMovesInfo[MOVE_STORM_THROW].power == 60);
ASSUME(gMovesInfo[MOVE_BRICK_BREAK].power == 75);
ASSUME(gMovesInfo[MOVE_STORM_THROW].type == gMovesInfo[MOVE_BRICK_BREAK].type);
ASSUME(gMovesInfo[MOVE_STORM_THROW].category == gMovesInfo[MOVE_BRICK_BREAK].category);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_OMASTAR) { Ability(ability); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_STORM_THROW, MOVE_BRICK_BREAK); }
} WHEN {
if (ability == ABILITY_SHELL_ARMOR)
TURN { EXPECT_MOVE(opponent, MOVE_BRICK_BREAK); }
else
TURN { EXPECT_MOVE(opponent, MOVE_STORM_THROW); }
}
}
AI_DOUBLE_BATTLE_TEST("AI recognizes Volt Absorb received from Trace")
{
KNOWN_FAILING; // MGriffin's PR that switched two turn charging moves in AI tests broke this test, waiting on a fix
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_MAGNETON);
PLAYER(SPECIES_GARDEVOIR) { Ability(ABILITY_TRACE); }
OPPONENT(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_THUNDER_WAVE, MOVE_THUNDERSHOCK, MOVE_WATER_GUN); }
OPPONENT(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_THUNDER_WAVE, MOVE_THUNDERSHOCK, MOVE_WATER_GUN); }
} WHEN {
TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDERSHOCK); NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDER_WAVE); NOT_EXPECT_MOVE(opponentRight, MOVE_THUNDER_WAVE); }
} THEN {
EXPECT(gBattleResources->aiData->abilities[B_POSITION_PLAYER_RIGHT] == ABILITY_VOLT_ABSORB);
}
}
AI_SINGLE_BATTLE_TEST("AI avoids contact moves against rocky helmet")
{
u32 item;
PARAMETRIZE { item = ITEM_NONE; }
PARAMETRIZE { item = ITEM_ROCKY_HELMET; }
GIVEN {
ASSUME(gMovesInfo[MOVE_BRANCH_POKE].makesContact);
ASSUME(!gMovesInfo[MOVE_LEAFAGE].makesContact);
ASSUME(gMovesInfo[MOVE_BRANCH_POKE].power == gMovesInfo[MOVE_LEAFAGE].power);
ASSUME(gMovesInfo[MOVE_BRANCH_POKE].type == gMovesInfo[MOVE_LEAFAGE].type);
ASSUME(gMovesInfo[MOVE_BRANCH_POKE].category == gMovesInfo[MOVE_LEAFAGE].category);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_WOBBUFFET) { Item(item); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_BRANCH_POKE, MOVE_LEAFAGE); }
} WHEN {
if (item == ITEM_ROCKY_HELMET)
TURN { EXPECT_MOVE(opponent, MOVE_LEAFAGE); }
else
TURN { EXPECT_MOVES(opponent, MOVE_LEAFAGE, MOVE_BRANCH_POKE); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses a guaranteed KO move instead of the move with the highest expected damage")
{
u32 flags;
PARAMETRIZE { flags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY; }
PARAMETRIZE { flags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT; }
GIVEN {
ASSUME(gMovesInfo[MOVE_SLASH].criticalHitStage == 1);
ASSUME(gMovesInfo[MOVE_SLASH].power == 70);
ASSUME(gMovesInfo[MOVE_STRENGTH].power == 80);
ASSUME(gMovesInfo[MOVE_SLASH].type == gMovesInfo[MOVE_STRENGTH].type);
ASSUME(gMovesInfo[MOVE_SLASH].category == gMovesInfo[MOVE_STRENGTH].category);
AI_FLAGS(flags);
PLAYER(SPECIES_WOBBUFFET) { HP(225); }
OPPONENT(SPECIES_ABSOL) { Ability(ABILITY_SUPER_LUCK); Moves(MOVE_SLASH, MOVE_STRENGTH); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_SLASH); }
if (flags & AI_FLAG_TRY_TO_FAINT)
TURN { EXPECT_MOVE(opponent, MOVE_STRENGTH); }
else
TURN { EXPECT_MOVE(opponent, MOVE_SLASH); }
} SCENE {
if (flags & AI_FLAG_TRY_TO_FAINT)
MESSAGE("Wobbuffet fainted!");
else
NOT MESSAGE("Wobbuffet fainted!");
}
}