From 48552f6c12e2b496d14d2bbbd1b7236008c76994 Mon Sep 17 00:00:00 2001 From: cawtds <38510667+cawtds@users.noreply.github.com> Date: Tue, 23 Jul 2024 19:54:49 +0200 Subject: [PATCH] update tests/test framework + failed test --- gflib/malloc.c | 2 +- include/data.h | 7 +- include/test/battle.h | 11 +- include/test/test.h | 30 +-- src/battle_gimmick.c | 13 +- src/battle_terastal.c | 27 +-- src/data/pokemon/species_info.h | 6 +- .../pokemon/species_info/shared_dex_text.h | 9 +- test/battle/ability/beads_of_ruin.c | 2 +- test/battle/ability/comatose.c | 54 ++++++ test/battle/ability/corrosion.c | 111 ++++++++++- test/battle/ability/cursed_body.c | 17 ++ test/battle/ability/download.c | 5 +- test/battle/ability/full_metal_body.c | 4 + test/battle/ability/ice_body.c | 2 +- test/battle/ability/illusion.c | 24 +++ test/battle/ability/intimidate.c | 57 ++++-- test/battle/ability/lightning_rod.c | 73 +++++++ test/battle/ability/minds_eye.c | 4 +- test/battle/ability/opportunist.c | 183 +++++++++++++++++- test/battle/ability/own_tempo.c | 17 ++ test/battle/ability/parental_bond.c | 24 +++ test/battle/ability/poison_touch.c | 77 ++++++++ test/battle/ability/sticky_hold.c | 18 ++ test/battle/ability/storm_drain.c | 73 +++++++ test/battle/ability/supreme_overlord.c | 2 +- test/battle/ability/switch_in_abilities.c | 128 ++++++++++++ test/battle/ability/sword_of_ruin.c | 2 +- test/battle/ability/tablets_of_ruin.c | 2 +- test/battle/ability/teraform_zero.c | 102 ++++++++++ test/battle/ability/trace.c | 17 ++ test/battle/ability/vessel_of_ruin.c | 2 +- test/battle/ability/white_smoke.c | 4 + test/battle/ability/zero_to_hero.c | 2 +- test/battle/{ => ai}/ai.c | 8 +- .../battle/{ => ai}/ai_calc_best_move_score.c | 0 test/battle/{ => ai}/ai_check_viability.c | 0 test/battle/{ => ai}/ai_choice.c | 32 +-- test/battle/{ => ai}/ai_flag_risky.c | 20 +- test/battle/ai/ai_flag_sequence_switching.c | 129 ++++++++++++ test/battle/{ => ai}/ai_powerful_status.c | 0 test/battle/{ => ai}/ai_switching.c | 35 ++-- test/battle/{ => ai}/ai_trytofaint.c | 0 test/battle/form_change/mega_evolution.c | 23 +++ test/battle/form_change/status.c | 2 +- test/battle/gimmick/terastal.c | 121 ++++++++++-- test/battle/hold_effect/cure_status.c | 18 +- test/battle/hold_effect/mirror_herb.c | 15 ++ test/battle/item_effect/cure_status.c | 120 ++++++------ .../battle/item_effect/heal_and_cure_status.c | 52 ++--- test/battle/move_effect/acrobatics.c | 47 ++++- test/battle/move_effect/attack_accuracy_up.c | 16 +- test/battle/move_effect/aura_wheel.c | 4 +- test/battle/move_effect/embargo.c | 12 +- test/battle/move_effect/flame_burst.c | 24 +++ test/battle/move_effect/focus_punch.c | 25 +++ test/battle/move_effect/fury_cutter.c | 38 ++++ test/battle/move_effect/hit_escape.c | 4 +- test/battle/move_effect/ion_deluge.c | 2 +- test/battle/move_effect/photon_geyser.c | 2 +- test/battle/move_effect/plasma_fists.c | 11 +- test/battle/move_effect/roost.c | 72 +++---- test/battle/move_effect/shell_side_arm.c | 4 +- test/battle/move_effect/spicy_extract.c | 1 - test/battle/move_effect/sticky_web.c | 10 +- test/battle/move_effect/strength_sap.c | 10 +- test/battle/move_effect/tera_starstorm.c | 15 ++ test/battle/move_effect/tidy_up.c | 4 +- test/battle/move_effect/toxic.c | 18 ++ test/battle/move_effect/two_turns_attack.c | 32 +-- test/battle/move_effect/upper_hand.c | 16 ++ test/battle/move_effect/weather_ball.c | 18 +- test/battle/move_effect_secondary/dire_claw.c | 3 +- test/battle/move_effect_secondary/freeze.c | 24 ++- .../battle/move_effect_secondary/tri_attack.c | 32 ++- .../move_effects_combined/flinch_status.c | 4 +- test/battle/move_effects_combined/hurricane.c | 40 +++- .../move_flags/ignores_target_ability.c | 74 +++++++ test/battle/status1/burn.c | 20 ++ test/battle/status1/paralysis.c | 18 ++ test/battle/status1/sleep.c | 18 ++ test/battle/status2/confusion.c | 28 +++ test/battle/terrain/grassy.c | 24 +++ test/battle/test_runner_features.c | 27 +++ test/battle/trainer_control.c | 47 +++++ test/battle/trainer_control.h | 2 +- test/battle/weather/hail.c | 17 ++ test/battle/weather/sandstorm.c | 21 +- test/battle/weather/snow.c | 4 +- test/species.c | 16 ++ test/test_runner.c | 27 ++- test/test_runner_battle.c | 128 ++++++------ test/text.c | 21 +- tools/mgba-rom-test-hydra/main.c | 115 ++++++++--- 94 files changed, 2340 insertions(+), 441 deletions(-) create mode 100644 test/battle/ability/comatose.c create mode 100644 test/battle/ability/cursed_body.c create mode 100644 test/battle/ability/full_metal_body.c create mode 100644 test/battle/ability/illusion.c create mode 100644 test/battle/ability/lightning_rod.c create mode 100644 test/battle/ability/poison_touch.c create mode 100644 test/battle/ability/sticky_hold.c create mode 100644 test/battle/ability/storm_drain.c create mode 100644 test/battle/ability/switch_in_abilities.c create mode 100644 test/battle/ability/teraform_zero.c create mode 100644 test/battle/ability/white_smoke.c rename test/battle/{ => ai}/ai.c (99%) rename test/battle/{ => ai}/ai_calc_best_move_score.c (100%) rename test/battle/{ => ai}/ai_check_viability.c (100%) rename test/battle/{ => ai}/ai_choice.c (75%) rename test/battle/{ => ai}/ai_flag_risky.c (89%) create mode 100644 test/battle/ai/ai_flag_sequence_switching.c rename test/battle/{ => ai}/ai_powerful_status.c (100%) rename test/battle/{ => ai}/ai_switching.c (89%) rename test/battle/{ => ai}/ai_trytofaint.c (100%) create mode 100644 test/battle/move_effect/flame_burst.c create mode 100644 test/battle/move_effect/fury_cutter.c create mode 100644 test/battle/move_flags/ignores_target_ability.c create mode 100644 test/battle/status2/confusion.c create mode 100644 test/battle/test_runner_features.c diff --git a/gflib/malloc.c b/gflib/malloc.c index a6bf10697..1527ce34c 100644 --- a/gflib/malloc.c +++ b/gflib/malloc.c @@ -99,7 +99,7 @@ void *AllocInternal(void *heapStart, u32 size, const char *location) block = block->next; } while (block != head); - Test_ExitWithResult(TEST_RESULT_ERROR, "%s: OOM allocating %d bytes", location, size); + Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":L%s:%d, %s: OOM allocating %d bytes", gTestRunnerState.test->filename, SourceLine(0), location, size); #endif AGB_ASSERT_EX(0, ABSPATH("gflib/malloc.c"), 174); return NULL; diff --git a/include/data.h b/include/data.h index 1f68d61e0..e9a9ab95a 100644 --- a/include/data.h +++ b/include/data.h @@ -48,11 +48,12 @@ struct TrainerMon u8 nature:5; bool8 gender:2; bool8 isShiny:1; - u8 useGimmick:4; - u8 dynamaxLevel:4; u8 teraType:5; bool8 gigantamaxFactor:1; - u8 padding:2; + u8 shouldUseDynamax:1; + u8 padding1:1; + u8 dynamaxLevel:4; + u8 padding2:4; }; #define TRAINER_PARTY(partyArray) partyArray, .partySize = ARRAY_COUNT(partyArray) diff --git a/include/test/battle.h b/include/test/battle.h index 6d105a3d5..a44e813f8 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -507,7 +507,7 @@ // or loop. #define BATTLE_TEST_STACK_SIZE 1024 #define MAX_TURNS 16 -#define MAX_QUEUED_EVENTS 25 +#define MAX_QUEUED_EVENTS 30 #define MAX_EXPECTED_ACTIONS 10 enum { BATTLE_TEST_SINGLES, BATTLE_TEST_DOUBLES, BATTLE_TEST_WILD, BATTLE_TEST_AI_SINGLES, BATTLE_TEST_AI_DOUBLES }; @@ -518,7 +518,6 @@ typedef void (*DoubleBattleTestFunction)(void *, const u32, struct BattlePokemon struct BattleTest { u8 type; - u16 sourceLine; union { SingleBattleTestFunction singles; @@ -744,10 +743,10 @@ extern struct BattleTestRunnerState *const gBattleTestRunnerState; .name = _name, \ .filename = __FILE__, \ .runner = &gBattleTestRunner, \ + .sourceLine = __LINE__, \ .data = (void *)&(const struct BattleTest) \ { \ .type = _type, \ - .sourceLine = __LINE__, \ .function = { .singles = (SingleBattleTestFunction)CAT(Test, __LINE__) }, \ .resultsSize = sizeof(struct CAT(Result, __LINE__)), \ }, \ @@ -762,10 +761,10 @@ extern struct BattleTestRunnerState *const gBattleTestRunnerState; .name = _name, \ .filename = __FILE__, \ .runner = &gBattleTestRunner, \ + .sourceLine = __LINE__, \ .data = (void *)&(const struct BattleTest) \ { \ .type = _type, \ - .sourceLine = __LINE__, \ .function = { .doubles = (DoubleBattleTestFunction)CAT(Test, __LINE__) }, \ .resultsSize = sizeof(struct CAT(Result, __LINE__)), \ }, \ @@ -995,6 +994,8 @@ void SendOut(u32 sourceLine, struct BattlePokemon *, u32 partyIndex); // Static const is needed to make the modern compiler put the pattern variable in the .rodata section, instead of putting it on stack(which can break the game). #define MESSAGE(pattern) do {static const u8 msg[] = _(pattern); QueueMessage(__LINE__, msg);} while (0) #define STATUS_ICON(battler, status) QueueStatus(__LINE__, battler, (struct StatusEventContext) { status }) +#define FREEZE_OR_FROSTBURN_STATUS(battler, isFrostbite) \ + (B_USE_FROSTBITE ? STATUS_ICON(battler, frostbite: isFrostbite) : STATUS_ICON(battler, freeze: isFrostbite)) #define SWITCH_OUT_MESSAGE(name) ONE_OF { \ MESSAGE(name ", that's enough! Come back!"); \ @@ -1092,7 +1093,7 @@ void ValidateFinally(u32 sourceLine); s32 _am = Q_4_12_TO_INT(_a * _m); \ s32 _t = max(Q_4_12_TO_INT(abs(_m) + Q_4_12_ROUND), 1); \ if (abs(_am-_b) > _t) \ - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_MUL_EQ(%d, %q, %d) failed: %d not in [%d..%d]", gTestRunnerState.test->filename, __LINE__, _a, _m, _b, _am, _b-_t, _b+_t); \ + Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_MUL_EQ(%d, %q, %d) failed: %d not in [%d..%d]", gTestRunnerState.test->filename, __LINE__, _a, _m, _b, _am, _b-_t, _b+_t); \ } while (0) #endif diff --git a/include/test/test.h b/include/test/test.h index 5a543a52b..401c65ce9 100644 --- a/include/test/test.h +++ b/include/test/test.h @@ -33,6 +33,7 @@ struct Test const char *filename; const struct TestRunner *runner; void *data; + u16 sourceLine; }; struct TestRunnerState @@ -40,6 +41,7 @@ struct TestRunnerState u8 state; u8 exitCode; const char *skipFilename; + u32 failedAssumptionsBlockLine; const struct Test *test; u32 processCosts[MAX_PROCESSES]; @@ -73,7 +75,9 @@ void CB2_TestRunner(void); void Test_ExpectedResult(enum TestResult); void Test_ExpectLeaks(bool32); -void Test_ExitWithResult(enum TestResult, const char *fmt, ...); +void Test_ExitWithResult(enum TestResult, u32 stopLine, const char *fmt, ...); +u32 SourceLine(u32 sourceLineOffset); +u32 SourceLineOffset(u32 sourceLine); s32 Test_MgbaPrintf(const char *fmt, ...); @@ -84,6 +88,7 @@ s32 Test_MgbaPrintf(const char *fmt, ...); .name = _name, \ .filename = __FILE__, \ .runner = &gFunctionTestRunner, \ + .sourceLine = __LINE__, \ .data = (void *)CAT(Test, __LINE__), \ }; \ static void CAT(Test, __LINE__)(void) @@ -95,6 +100,7 @@ s32 Test_MgbaPrintf(const char *fmt, ...); .name = "ASSUMPTIONS: " __FILE__, \ .filename = __FILE__, \ .runner = &gAssumptionsRunner, \ + .sourceLine = __LINE__, \ .data = Assumptions, \ }; \ static void Assumptions(void) @@ -103,14 +109,14 @@ s32 Test_MgbaPrintf(const char *fmt, ...); do \ { \ if (!(c)) \ - Test_ExitWithResult(TEST_RESULT_ASSUMPTION_FAIL, "%s:%d: ASSUME failed", gTestRunnerState.test->filename, __LINE__); \ + Test_ExitWithResult(TEST_RESULT_ASSUMPTION_FAIL, __LINE__, ":L%s:%d: ASSUME failed", gTestRunnerState.test->filename, __LINE__); \ } while (0) #define EXPECT(c) \ do \ { \ if (!(c)) \ - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT failed", gTestRunnerState.test->filename, __LINE__); \ + Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT failed", gTestRunnerState.test->filename, __LINE__); \ } while (0) #define EXPECT_EQ(a, b) \ @@ -118,7 +124,7 @@ s32 Test_MgbaPrintf(const char *fmt, ...); { \ typeof(a) _a = (a), _b = (b); \ if (_a != _b) \ - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_EQ(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \ + Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_EQ(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \ } while (0) #define EXPECT_NE(a, b) \ @@ -126,7 +132,7 @@ s32 Test_MgbaPrintf(const char *fmt, ...); { \ typeof(a) _a = (a), _b = (b); \ if (_a == _b) \ - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_NE(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \ + Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_NE(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \ } while (0) #define EXPECT_LT(a, b) \ @@ -134,7 +140,7 @@ s32 Test_MgbaPrintf(const char *fmt, ...); { \ typeof(a) _a = (a), _b = (b); \ if (_a >= _b) \ - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_LT(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \ + Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_LT(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \ } while (0) #define EXPECT_LE(a, b) \ @@ -142,7 +148,7 @@ s32 Test_MgbaPrintf(const char *fmt, ...); { \ typeof(a) _a = (a), _b = (b); \ if (_a > _b) \ - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_LE(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \ + Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_LE(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \ } while (0) #define EXPECT_GT(a, b) \ @@ -150,7 +156,7 @@ s32 Test_MgbaPrintf(const char *fmt, ...); { \ typeof(a) _a = (a), _b = (b); \ if (_a <= _b) \ - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_GT(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \ + Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_GT(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \ } while (0) #define EXPECT_GE(a, b) \ @@ -158,7 +164,7 @@ s32 Test_MgbaPrintf(const char *fmt, ...); { \ typeof(a) _a = (a), _b = (b); \ if (_a < _b) \ - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_GE(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \ + Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_GE(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \ } while (0) struct Benchmark { s32 ticks; }; @@ -195,7 +201,7 @@ static inline struct Benchmark BenchmarkStop(void) u32 a_ = (a).ticks; u32 b_ = (b).ticks; \ Test_MgbaPrintf(#a ": %d ticks, " #b ": %d ticks", a_, b_); \ if (((a_ - BENCHMARK_ABS) * BENCHMARK_REL) >= (b_ * 100)) \ - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_FASTER(" #a ", " #b ") failed", gTestRunnerState.test->filename, __LINE__); \ + Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_FASTER(" #a ", " #b ") failed", gTestRunnerState.test->filename, __LINE__); \ } while (0) #define EXPECT_SLOWER(a, b) \ @@ -204,7 +210,7 @@ static inline struct Benchmark BenchmarkStop(void) u32 a_ = (a).ticks; u32 b_ = (b).ticks; \ Test_MgbaPrintf(#a ": %d ticks, " #b ": %d ticks", a_, b_); \ if ((a_ * 100) <= ((b_ - BENCHMARK_ABS) * BENCHMARK_REL)) \ - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_SLOWER(" #a ", " #b ") failed", gTestRunnerState.test->filename, __LINE__); \ + Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_SLOWER(" #a ", " #b ") failed", gTestRunnerState.test->filename, __LINE__); \ } while (0) #define KNOWN_FAILING \ @@ -220,7 +226,7 @@ static inline struct Benchmark BenchmarkStop(void) #define TO_DO \ do { \ Test_ExpectedResult(TEST_RESULT_TODO); \ - Test_ExitWithResult(TEST_RESULT_TODO, "%s:%d: EXPECT_TO_DO", gTestRunnerState.test->filename, __LINE__); \ + Test_ExitWithResult(TEST_RESULT_TODO, __LINE__, ":L%s:%d: EXPECT_TO_DO", gTestRunnerState.test->filename, __LINE__); \ } while (0) #endif diff --git a/src/battle_gimmick.c b/src/battle_gimmick.c index 995c68f2a..c8ee93221 100644 --- a/src/battle_gimmick.c +++ b/src/battle_gimmick.c @@ -82,8 +82,14 @@ bool32 ShouldTrainerBattlerUseGimmick(u32 battler, enum Gimmick gimmick) bool32 isSecondTrainer = (GetBattlerPosition(battler) == B_POSITION_OPPONENT_RIGHT) && (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) && !BATTLE_TWO_VS_ONE_OPPONENT; u16 trainerId = isSecondTrainer ? gTrainerBattleOpponent_B : gTrainerBattleOpponent_A; const struct TrainerMon *mon = &GetTrainerPartyFromId(trainerId)[isSecondTrainer ? gBattlerPartyIndexes[battler] - MULTI_PARTY_SIZE : gBattlerPartyIndexes[battler]]; - return mon->useGimmick == gimmick; + + if (gimmick == GIMMICK_TERA && mon->teraType != TYPE_NONE) + return TRUE; + if (gimmick == GIMMICK_DYNAMAX && mon->shouldUseDynamax) + return TRUE; } + + return FALSE; } // Returns whether a trainer has used a gimmick during a battle. @@ -290,7 +296,7 @@ static inline u32 GetIndicatorSpriteId(u32 healthboxId) u32 GetIndicatorTileTag(u32 battler) { u32 gimmick = GetActiveGimmick(battler); - + if (IsBattlerPrimalReverted(battler)) { if (gBattleMons[battler].species == SPECIES_GROUDON_PRIMAL) @@ -330,6 +336,9 @@ void UpdateIndicatorVisibilityAndType(u32 healthboxId, bool32 invisible) u32 palTag = GetIndicatorPalTag(battler); struct Sprite *sprite = &gSprites[GetIndicatorSpriteId(healthboxId)]; + if (GetIndicatorSpriteId(healthboxId) == 0) // safari zone means the player doesn't have an indicator sprite id + return; + if (tileTag != TAG_NONE && palTag != TAG_NONE) { sprite->oam.tileNum = GetSpriteTileStartByTag(tileTag); diff --git a/src/battle_terastal.c b/src/battle_terastal.c index ff01ee259..6866c6aaa 100644 --- a/src/battle_terastal.c +++ b/src/battle_terastal.c @@ -34,7 +34,7 @@ void ActivateTera(u32 battler) { FlagClear(B_FLAG_TERA_ORB_CHARGED); } - + // Execute battle script. PREPARE_TYPE_BUFFER(gBattleTextBuff1, GetBattlerTeraType(battler)); if (TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_TERASTALLIZATION)) @@ -63,16 +63,21 @@ bool32 CanTerastallize(u32 battler) { u32 holdEffect = GetBattlerHoldEffect(battler, FALSE); - // Check if Player has Tera Orb and has charge. - if (!TESTING && !CheckBagHasItem(ITEM_TERA_ORB, 1)) - return FALSE; - - if (!TESTING - && !(B_FLAG_TERA_ORB_NO_COST != 0 && FlagGet(B_FLAG_TERA_ORB_NO_COST)) - && (battler == B_POSITION_PLAYER_LEFT || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && battler == B_POSITION_PLAYER_RIGHT))) + if (TESTING || GetBattlerSide(battler) == B_SIDE_OPPONENT) { - if (B_FLAG_TERA_ORB_CHARGED != 0 && !FlagGet(B_FLAG_TERA_ORB_CHARGED)) - return FALSE; + // Skip all other checks in this block, go to HasTrainerUsedGimmick + } + else if (!CheckBagHasItem(ITEM_TERA_ORB, 1)) + { + return FALSE; + } + else if (FlagGet(B_FLAG_TERA_ORB_NO_COST)) + { + // Tera Orb is not depleted, go to HasTrainerUsedGimmick + } + else if (!FlagGet(B_FLAG_TERA_ORB_CHARGED)) + { + return FALSE; } // Check if Trainer has already Terastallized. @@ -104,7 +109,7 @@ u32 GetBattlerTeraType(u32 battler) // Uses up a type's Stellar boost. void ExpendTypeStellarBoost(u32 battler, u32 type) { - if (type < 32) // avoid OOB access + if (type < 32 && gBattleMons[battler].species != SPECIES_TERAPAGOS_STELLAR) // avoid OOB access gBattleStruct->stellarBoostFlags[GetBattlerSide(battler)] |= gBitTable[type]; } diff --git a/src/data/pokemon/species_info.h b/src/data/pokemon/species_info.h index 5f0ffad73..7e9c85e09 100644 --- a/src/data/pokemon/species_info.h +++ b/src/data/pokemon/species_info.h @@ -76,11 +76,7 @@ const struct SpeciesInfo gSpeciesInfo[] = .categoryName = _("Unknown"), .height = 0, .weight = 0, - .description = COMPOUND_STRING( - "This is a newly discovered Pokémon.\n" - "It is currently under investigation.\n" - "No detailed information is available\n" - "at this time."), + .description = gFallbackPokedexText, .pokemonScale = 256, .pokemonOffset = 0, .trainerScale = 256, diff --git a/src/data/pokemon/species_info/shared_dex_text.h b/src/data/pokemon/species_info/shared_dex_text.h index 0e4805153..0c591bd81 100644 --- a/src/data/pokemon/species_info/shared_dex_text.h +++ b/src/data/pokemon/species_info/shared_dex_text.h @@ -1,3 +1,10 @@ +// fallback +const u8 gFallbackPokedexText[] = _( + "This is a newly discovered Pokémon.\n" + "It is currently under investigation.\n" + "No detailed information is available\n" + "at this time."); + // Gen 1 families const u8 gRaticateAlolanPokedexText[] = _( "It forms a group of Rattata, which it \n" @@ -279,4 +286,4 @@ const u8 gOgerponCornerstoneMaskPokedexText[] = _( "In this form, it draws on the power\n" "of stone. Its body is rock-solid,\n" "protecting it from all manner of\n" - "attacks."); \ No newline at end of file + "attacks."); diff --git a/test/battle/ability/beads_of_ruin.c b/test/battle/ability/beads_of_ruin.c index a58297a58..bb05031fe 100644 --- a/test/battle/ability/beads_of_ruin.c +++ b/test/battle/ability/beads_of_ruin.c @@ -46,9 +46,9 @@ SINGLE_BATTLE_TEST("Beads of Ruin's message displays correctly after all battler ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); // Everyone faints. MESSAGE("Go! Chi-Yu!"); + MESSAGE("2 sent out Wobbuffet!"); ABILITY_POPUP(player, ABILITY_BEADS_OF_RUIN); MESSAGE("Chi-Yu's Beads of Ruin weakened the Sp. Def of all surrounding Pokémon!"); - MESSAGE("2 sent out Wobbuffet!"); } } diff --git a/test/battle/ability/comatose.c b/test/battle/ability/comatose.c new file mode 100644 index 000000000..bd991c258 --- /dev/null +++ b/test/battle/ability/comatose.c @@ -0,0 +1,54 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Comatose prevents status-inducing moves") +{ + u32 move; + + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISONPOWDER; } + PARAMETRIZE { move = MOVE_SLEEP_POWDER; } + PARAMETRIZE { move = MOVE_THUNDER_WAVE; } + + GIVEN { + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_COMATOSE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + MESSAGE("Komala is drowsing!"); + + NOT ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ABILITY_POPUP(player, ABILITY_COMATOSE); + MESSAGE("It doesn't affect Komala…"); + } +} + +SINGLE_BATTLE_TEST("Comatose may be suppressed if pokemon transformed into a pokemon with Comatose ability and was under the effects of Gastro Acid") +{ + u32 move; + + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISONPOWDER; } + PARAMETRIZE { move = MOVE_SLEEP_POWDER; } + PARAMETRIZE { move = MOVE_THUNDER_WAVE; } + + GIVEN { + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_COMATOSE); Speed(30); } + OPPONENT(SPECIES_DITTO) { Speed(20); } + } WHEN { + TURN { MOVE(player, MOVE_GASTRO_ACID); MOVE(opponent, MOVE_TRANSFORM); } + TURN { MOVE(player, move); } + } SCENE { + MESSAGE("Komala is drowsing!"); + MESSAGE("Komala used Gastro Acid!"); + MESSAGE("Foe Ditto used Transform!"); + MESSAGE("Foe Ditto transformed into Komala!"); + + ANIMATION(ANIM_TYPE_MOVE, move, player); + if (move == MOVE_POISONPOWDER) { STATUS_ICON(opponent, poison: TRUE); } + else if (move == MOVE_TOXIC) { STATUS_ICON(opponent, badPoison: TRUE); } + else if (move == MOVE_THUNDER_WAVE) { STATUS_ICON(opponent, paralysis: TRUE); } + else if (move == MOVE_SLEEP_POWDER) { STATUS_ICON(opponent, sleep: TRUE); } + } +} diff --git a/test/battle/ability/corrosion.c b/test/battle/ability/corrosion.c index 87477ddbc..8addbd90f 100644 --- a/test/battle/ability/corrosion.c +++ b/test/battle/ability/corrosion.c @@ -106,23 +106,122 @@ SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion holds a T SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion poisons a target with Synchronize, Synchronize will not poison Poison- or Steel-type Pokémon") { + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } GIVEN { ASSUME(gMovesInfo[MOVE_TOXIC].effect == EFFECT_TOXIC); + ASSUME(gMovesInfo[MOVE_POISON_POWDER].effect == EFFECT_POISON); PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } OPPONENT(SPECIES_ABRA) { Ability(ABILITY_SYNCHRONIZE); } } WHEN { - TURN { MOVE(player, MOVE_TOXIC); } + TURN { MOVE(player, move); } } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_MOVE, move, player); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); - STATUS_ICON(opponent, badPoison: TRUE); + if (move == MOVE_TOXIC) + STATUS_ICON(opponent, badPoison: TRUE); + else + STATUS_ICON(opponent, poison: TRUE); NONE_OF { ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); STATUS_ICON(player, badPoison: TRUE); + STATUS_ICON(player, poison: TRUE); } } } -TO_DO_BATTLE_TEST("Corrosion cannot bypass moves or Abilities that prevent poisoning, such as Safeguard or Immunity"); -TO_DO_BATTLE_TEST("If the Pokémon with this Ability uses Magic Coat to reflect a status move that inflicts poison, the reflected move will be able to poison Poison- or Steel-type Pokémon."); -TO_DO_BATTLE_TEST("Moves used by a Pokémon with Corrosion that are reflected by Magic Coat or Magic Bounce do not retain the ability to poison Poison- or Steel-type Pokémon.") +SINGLE_BATTLE_TEST("Corrosion cannot bypass moves that prevent poisoning such as Safeguard") +{ + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TOXIC].effect == EFFECT_TOXIC); + ASSUME(gMovesInfo[MOVE_POISON_POWDER].effect == EFFECT_POISON); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SAFEGUARD); MOVE(player, move); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, badPoison: TRUE); + STATUS_ICON(opponent, poison: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Corrosion cannot bypass abilities that prevent poisoning such as Immunity") +{ + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TOXIC].effect == EFFECT_TOXIC); + ASSUME(gMovesInfo[MOVE_POISON_POWDER].effect == EFFECT_POISON); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } + OPPONENT(SPECIES_SNORLAX) { Ability(ABILITY_IMMUNITY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, badPoison: TRUE); + STATUS_ICON(opponent, poison: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Corrosion allows the Pokémon with the ability to poison a Steel or Poison-type opponent by using Magic Coat") +{ + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TOXIC].effect == EFFECT_TOXIC); + ASSUME(gMovesInfo[MOVE_POISON_POWDER].effect == EFFECT_POISON); + ASSUME(gMovesInfo[MOVE_MAGIC_COAT].effect == EFFECT_MAGIC_COAT); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } + OPPONENT(SPECIES_BELDUM); + } WHEN { + TURN { MOVE(player, MOVE_MAGIC_COAT); MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_COAT, player); + ANIMATION(ANIM_TYPE_MOVE, move, player); // Bounced by Magic Coat + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + if (move == MOVE_TOXIC) + STATUS_ICON(opponent, badPoison: TRUE); + else + STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Corrosion's effect is lost if the move used by the Pokémon with the ability is reflected by Magic Coat") +{ + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TOXIC].effect == EFFECT_TOXIC); + ASSUME(gMovesInfo[MOVE_POISON_POWDER].effect == EFFECT_POISON); + ASSUME(gMovesInfo[MOVE_MAGIC_COAT].effect == EFFECT_MAGIC_COAT); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_MAGIC_COAT); MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_COAT, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, move, player); + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + if (move == MOVE_TOXIC) + STATUS_ICON(opponent, badPoison: TRUE); + else + STATUS_ICON(opponent, poison: TRUE); + } + } +} diff --git a/test/battle/ability/cursed_body.c b/test/battle/ability/cursed_body.c new file mode 100644 index 000000000..20fe659d2 --- /dev/null +++ b/test/battle/ability/cursed_body.c @@ -0,0 +1,17 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Cursed Body triggers 30% of the time") +{ + PASSES_RANDOMLY(3, 10, RNG_CURSED_BODY); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_FRILLISH) { Ability(ABILITY_CURSED_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_AQUA_JET); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, player); + ABILITY_POPUP(opponent, ABILITY_CURSED_BODY); + MESSAGE("Wobbuffet's Aqua Jet was disabled by Foe Frillish's Cursed Body!"); + } +} diff --git a/test/battle/ability/download.c b/test/battle/ability/download.c index 2d9f8f466..9fecda400 100644 --- a/test/battle/ability/download.c +++ b/test/battle/ability/download.c @@ -56,6 +56,7 @@ SINGLE_BATTLE_TEST("Download raises Sp.Attack if enemy has lower Sp. Def than De SINGLE_BATTLE_TEST("Download doesn't activate if target hasn't been sent out yet", s16 damagePhysical, s16 damageSpecial) { u32 ability; + PARAMETRIZE { ability = ABILITY_TRACE; } PARAMETRIZE { ability = ABILITY_DOWNLOAD; } GIVEN { @@ -73,13 +74,13 @@ SINGLE_BATTLE_TEST("Download doesn't activate if target hasn't been sent out yet // Everyone faints. SEND_IN_MESSAGE("Porygon"); - MESSAGE("2 sent out Porygon2!"); - NONE_OF { ABILITY_POPUP(player, ABILITY_DOWNLOAD); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Porygon's Download raised its Attack!"); } + MESSAGE("2 sent out Porygon2!"); + if (ability == ABILITY_DOWNLOAD) { ABILITY_POPUP(opponent, ABILITY_DOWNLOAD); diff --git a/test/battle/ability/full_metal_body.c b/test/battle/ability/full_metal_body.c new file mode 100644 index 000000000..3b36f2d1f --- /dev/null +++ b/test/battle/ability/full_metal_body.c @@ -0,0 +1,4 @@ +#include "global.h" +#include "test/battle.h" + +// Tests for Full Metal Body are handled in test/battle/ability/clear_body.c diff --git a/test/battle/ability/ice_body.c b/test/battle/ability/ice_body.c index aca84532a..1aa7c80a4 100644 --- a/test/battle/ability/ice_body.c +++ b/test/battle/ability/ice_body.c @@ -22,7 +22,7 @@ SINGLE_BATTLE_TEST("Ice Body recovers 1/16th of Max HP in hail.") TURN { MOVE(opponent, MOVE_HAIL); } } SCENE { ABILITY_POPUP(player, ABILITY_ICE_BODY); - MESSAGE("Glalie's Ice Body healed it a little bit!"); HP_BAR(player, damage: -(100 / 16)); + MESSAGE("Glalie's Ice Body healed it a little bit!"); } } diff --git a/test/battle/ability/illusion.c b/test/battle/ability/illusion.c new file mode 100644 index 000000000..ca3bcb2ee --- /dev/null +++ b/test/battle/ability/illusion.c @@ -0,0 +1,24 @@ +#include "global.h" +#include "test/battle.h" + +// This test is eyes on only +SINGLE_BATTLE_TEST("Illusion can only imitate Normal Form terapagos") +{ + GIVEN { + PLAYER(SPECIES_ZOROARK) { Moves(MOVE_CELEBRATE); } + PLAYER(SPECIES_TERAPAGOS) { Moves(MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + // Zoroark is out, should be normal form Terapagos + // Switch to Terapagos which enters Terastal Form + TURN { SWITCH(player, 1); } + // Switch back to Zoroark, should not be Terastal Terapagos + TURN { SWITCH(player, 0); MOVE(opponent, MOVE_TACKLE);} + // Switch back to Terapagos + TURN { SWITCH(player, 1); } + // Terapagos Stellar, Zoroark gets Roared in, should not be Stellar Terapagos + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_ROAR); } + // Reveal the Zoroark + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TACKLE); } + } +} diff --git a/test/battle/ability/intimidate.c b/test/battle/ability/intimidate.c index f4c539572..d2d7bc4af 100644 --- a/test/battle/ability/intimidate.c +++ b/test/battle/ability/intimidate.c @@ -78,23 +78,29 @@ DOUBLE_BATTLE_TEST("Intimidate doesn't activate on an empty field in a double ba // Everyone faints. SEND_IN_MESSAGE("Ekans"); - MESSAGE("2 sent out Arbok!"); - SEND_IN_MESSAGE("Abra"); - MESSAGE("2 sent out Wynaut!"); - NONE_OF { ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); - MESSAGE("Ekans's Intimidate cuts Foe Arbok's attack!"); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); - MESSAGE("Ekans's Intimidate cuts Foe Wynaut's attack!"); - + } + MESSAGE("2 sent out Arbok!"); + NONE_OF { ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); - MESSAGE("Foe Arbok's Intimidate cuts Ekans's attack!"); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); - MESSAGE("Foe Arbok's Intimidate cuts Abra's attack!"); } + SEND_IN_MESSAGE("Abra"); + MESSAGE("2 sent out Wynaut!"); + // Intimidate activates after all battlers have been brought out + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Ekans's Intimidate cuts Foe Arbok's attack!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Ekans's Intimidate cuts Foe Wynaut's attack!"); + + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Foe Arbok's Intimidate cuts Ekans's attack!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Foe Arbok's Intimidate cuts Abra's attack!"); } } @@ -211,6 +217,35 @@ SINGLE_BATTLE_TEST("Intimidate can not further lower opponents Atk stat if it is } } +DOUBLE_BATTLE_TEST("Intimidate is not going to trigger if a mon switches out through u-turn and the opposing field is empty") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TREECKO); + OPPONENT(SPECIES_TORCHIC); + } WHEN { + TURN { + MOVE(opponentRight, MOVE_HEALING_WISH); + MOVE(playerLeft, MOVE_U_TURN, target: opponentLeft); + SEND_OUT(playerLeft, 2); + SEND_OUT(opponentLeft, 2); + SEND_OUT(opponentRight, 3); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEALING_WISH, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, playerLeft); + HP_BAR(opponentLeft); + MESSAGE("2 sent out Treecko!"); + MESSAGE("2 sent out Torchic!"); + NOT ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + } +} + SINGLE_BATTLE_TEST("Intimidate activates when it's no longer effected by Neutralizing Gas") { GIVEN { diff --git a/test/battle/ability/lightning_rod.c b/test/battle/ability/lightning_rod.c new file mode 100644 index 000000000..33dd27a6f --- /dev/null +++ b/test/battle/ability/lightning_rod.c @@ -0,0 +1,73 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Lightning Rod absorbs Electric-type moves and increases the Sp. Attack [Gen5+]") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_THUNDERBOLT].type == TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_LIGHTNING_ROD); } + } WHEN { + TURN { MOVE(player, MOVE_THUNDERBOLT); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player); + HP_BAR(opponent); + }; + ABILITY_POPUP(opponent, ABILITY_LIGHTNING_ROD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Raichu's Sp. Atk rose!"); + } else { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_LIGHTNING_ROD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Raichu's Sp. Atk rose!"); + }; + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player); + HP_BAR(opponent); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +DOUBLE_BATTLE_TEST("Lightning Rod forces single-target Electric-type moves to target the Pokémon with this Ability.") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_THUNDERBOLT].type == TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_LIGHTNING_ROD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_THUNDERBOLT, target: opponentRight); + MOVE(playerRight, MOVE_THUNDERBOLT, target: opponentRight); + MOVE(opponentLeft, MOVE_CELEBRATE); + MOVE(opponentRight, MOVE_CELEBRATE); + } + } SCENE { + if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) { + NONE_OF { + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + }; + ABILITY_POPUP(opponentLeft, ABILITY_LIGHTNING_ROD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Foe Raichu's Sp. Atk rose!"); + ABILITY_POPUP(opponentLeft, ABILITY_LIGHTNING_ROD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Foe Raichu's Sp. Atk rose!"); + } else { + NONE_OF { + HP_BAR(opponentRight); + }; + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, playerRight); + HP_BAR(opponentLeft); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + } +} diff --git a/test/battle/ability/minds_eye.c b/test/battle/ability/minds_eye.c index 6498f9f09..7799735ee 100644 --- a/test/battle/ability/minds_eye.c +++ b/test/battle/ability/minds_eye.c @@ -49,8 +49,8 @@ AI_SINGLE_BATTLE_TEST("AI doesn't use accuracy-lowering moves if it knows that t for (j = MOVE_NONE + 1; j < MOVES_COUNT; j++) { if (gMovesInfo[j].effect == EFFECT_ACCURACY_DOWN || gMovesInfo[j].effect == EFFECT_ACCURACY_DOWN_2) { - PARAMETRIZE{ moveAI = j; abilityAI = ABILITY_SWIFT_SWIM; } - PARAMETRIZE{ moveAI = j; abilityAI = ABILITY_MOLD_BREAKER; } + PARAMETRIZE { moveAI = j; abilityAI = ABILITY_SWIFT_SWIM; } + PARAMETRIZE { moveAI = j; abilityAI = ABILITY_MOLD_BREAKER; } } } diff --git a/test/battle/ability/opportunist.c b/test/battle/ability/opportunist.c index f2a9f5a37..b627b8ec6 100644 --- a/test/battle/ability/opportunist.c +++ b/test/battle/ability/opportunist.c @@ -16,16 +16,16 @@ SINGLE_BATTLE_TEST("Opportunist only copies foe's positive stat changes in a tur OPPONENT(SPECIES_ESPATHRA) { Speed(5); Ability(ability); } } WHEN { TURN { MOVE(player, MOVE_SHELL_SMASH); } - TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_TACKLE); } + TURN { MOVE(opponent, MOVE_TACKLE); } } SCENE { if (ability == ABILITY_FRISK) { ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SMASH, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); HP_BAR(player, captureDamage: &results[i].damage); } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SMASH, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); HP_BAR(player, captureDamage: &results[i].damage); - ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); } } FINALLY { EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); @@ -78,12 +78,10 @@ DOUBLE_BATTLE_TEST("Opportunist raises Attack only once when partner has Intimid if ((abilityLeft == ABILITY_CONTRARY && abilityRight != ABILITY_CONTRARY) || (abilityLeft != ABILITY_CONTRARY && abilityRight == ABILITY_CONTRARY)) { ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST); - MESSAGE("Espathra copied its opponent's stat changes!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); MESSAGE("Espathra's Attack rose!"); } else if (abilityLeft == ABILITY_CONTRARY && abilityRight == ABILITY_CONTRARY) { ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST); - MESSAGE("Espathra copied its opponent's stat changes!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); MESSAGE("Espathra's Attack sharply rose!"); } @@ -124,6 +122,177 @@ SINGLE_BATTLE_TEST("Opportunist does not accumulate opposing mon's stat changes" } } -TO_DO_BATTLE_TEST("Opportunist doesn't copy ally stat increases"); -TO_DO_BATTLE_TEST("Opportunist doesn't copy foe stat increases gained via Opportunist"); -TO_DO_BATTLE_TEST("Opportunist copies foe stat increased gained via Swagger and Flatter"); +SINGLE_BATTLE_TEST("Opportunist copies each stat increase individually from ability and move") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); } + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); } + } WHEN { + TURN { SWITCH(player, 1); } + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_INTREPID_SWORD); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 3); + } +} + +SINGLE_BATTLE_TEST("Opportunist doesn't copy foe stat increases gained via Opportunist") +{ + GIVEN { + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); } + } WHEN { + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + NOT ABILITY_POPUP(player, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], player->statStages[STAT_ATK]); + } +} + +SINGLE_BATTLE_TEST("Opportunist copies foe stat increase gained via Swagger and Flatter") +{ + GIVEN { + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); } + } WHEN { + TURN { MOVE(opponent, MOVE_FLATTER); } + TURN { MOVE(opponent, MOVE_SWAGGER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLATTER, opponent); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWAGGER, opponent); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +DOUBLE_BATTLE_TEST("Opportunist doesn't copy ally stat increases") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SWORDS_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, playerLeft); + NOT ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(playerRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE ); + } +} + +DOUBLE_BATTLE_TEST("Opportunist copies the stat increase of each opposing mon") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentRight, MOVE_SWORDS_DANCE); MOVE(opponentLeft, MOVE_SWORDS_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponentLeft); + ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 4); + } +} + + +DOUBLE_BATTLE_TEST("Opportunist copies the stat of each pokemon that were raised at the same time") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); } + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_INTREPID_SWORD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ABILITY_POPUP(opponentRight, ABILITY_INTREPID_SWORD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST); + MESSAGE("Espathra's Attack sharply rose!"); + } THEN { + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Opportunist copies the increase not the stages") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); } + } WHEN { + TURN { MOVE(player, MOVE_CHARM); MOVE(opponent, MOVE_CHARM); } + TURN { MOVE(player, MOVE_CHARM); MOVE(opponent, MOVE_CHARM); } + TURN { MOVE(player, MOVE_CHARM); MOVE(opponent, MOVE_GROWL); } + TURN { MOVE(player, MOVE_BELLY_DRUM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, opponent); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, opponent); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, opponent); + + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 5); // + 11 + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 6); // + 11 + } +} + +SINGLE_BATTLE_TEST("Opportunist copies the stat increase from the incoming mon") +{ + GIVEN { + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ABILITY_POPUP(opponent, ABILITY_INTREPID_SWORD); + ABILITY_POPUP(player, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Opportunist and Mirror Herb stack stat increases") +{ + GIVEN { + PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); } + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); Item(ITEM_MIRROR_HERB); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_INTREPID_SWORD); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE); + } +} diff --git a/test/battle/ability/own_tempo.c b/test/battle/ability/own_tempo.c index d5a6f58a7..a37bc0024 100644 --- a/test/battle/ability/own_tempo.c +++ b/test/battle/ability/own_tempo.c @@ -56,6 +56,23 @@ SINGLE_BATTLE_TEST("Own Tempo prevents confusion from moves by the user") } } +SINGLE_BATTLE_TEST("Own Tempo is ignored by Mold Breaker") +{ + KNOWN_FAILING; // Ideally the func CanBeConfused should be split into AttackerCanBeConfused and TargetCanBeConfused or we do it in the same func but have a check for when battlerAtk == battlerDef + GIVEN { + ASSUME(gMovesInfo[MOVE_CONFUSE_RAY].effect == EFFECT_CONFUSE); + PLAYER(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_SLOWPOKE) { Ability(ABILITY_OWN_TEMPO); }; + } WHEN { + TURN { MOVE(player, MOVE_CONFUSE_RAY); } + } SCENE { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_OWN_TEMPO); + MESSAGE("Foe Slowpoke's Own Tempo prevents confusion!"); + } + } +} + SINGLE_BATTLE_TEST("Own Tempo cures confusion obtained from an opponent with Mold Breaker") { KNOWN_FAILING; diff --git a/test/battle/ability/parental_bond.c b/test/battle/ability/parental_bond.c index 0b8a807e8..945e975b7 100644 --- a/test/battle/ability/parental_bond.c +++ b/test/battle/ability/parental_bond.c @@ -239,6 +239,30 @@ SINGLE_BATTLE_TEST("Parental Bond has no affect on multi hit moves and they stil } } +SINGLE_BATTLE_TEST("Parental Bond Smack Down effect triggers after 2nd hit") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_SMACK_DOWN].category != DAMAGE_CATEGORY_STATUS); + ASSUME(gMovesInfo[MOVE_SMACK_DOWN].strikeCount < 2); + ASSUME(MoveHasAdditionalEffect(MOVE_SMACK_DOWN, MOVE_EFFECT_SMACK_DOWN)); + PLAYER(SPECIES_KANGASKHAN) { Item(ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_SKARMORY); + } WHEN { + TURN { MOVE(player, MOVE_SMACK_DOWN, gimmick: GIMMICK_MEGA); } + } SCENE { + MESSAGE("Kangaskhan's Kangaskhanite is reacting to 1's Mega Ring!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); + MESSAGE("Kangaskhan has Mega Evolved into Mega Kangaskhan!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SMACK_DOWN, player); + HP_BAR(opponent); + NOT MESSAGE("Foe Skarmory fell straight down!"); + HP_BAR(opponent); + MESSAGE("Foe Skarmory fell straight down!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_KANGASKHAN_MEGA); + } +} + TO_DO_BATTLE_TEST("Parental Bond tests"); // Temporary TODO: Convert Bulbapedia description into tests. diff --git a/test/battle/ability/poison_touch.c b/test/battle/ability/poison_touch.c new file mode 100644 index 000000000..b69fa2044 --- /dev/null +++ b/test/battle/ability/poison_touch.c @@ -0,0 +1,77 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Poison Touch has a 30% chance to poison when attacking with contact moves") +{ + PASSES_RANDOMLY(3, 10, RNG_POISON_TOUCH); + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].power > 0); + ASSUME(gMovesInfo[MOVE_TACKLE].makesContact); + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_POISON_TOUCH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("Foe Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Poison Touch only applies when using contact moves") +{ + u32 move; + + PARAMETRIZE { move = MOVE_TACKLE; } + PARAMETRIZE { move = MOVE_SWIFT; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].makesContact); + ASSUME(!gMovesInfo[MOVE_SWIFT].makesContact); + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_POISON_TOUCH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + if (gMovesInfo[move].makesContact) { + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("Foe Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } else { + NONE_OF { + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("Foe Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } + } + } +} + +SINGLE_BATTLE_TEST("Poison Touch applies between multi-hit move hits") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_ARM_THRUST].effect == EFFECT_MULTI_HIT); + ASSUME(gMovesInfo[MOVE_ARM_THRUST].makesContact); + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_POISON_TOUCH); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_PECHA_BERRY); }; + } WHEN { + TURN { MOVE(player, MOVE_ARM_THRUST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ARM_THRUST, player); + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("Foe Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + MESSAGE("Foe Wobbuffet's Pecha Berry cured poison!"); + STATUS_ICON(opponent, poison: FALSE); + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("Foe Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } +} diff --git a/test/battle/ability/sticky_hold.c b/test/battle/ability/sticky_hold.c new file mode 100644 index 000000000..2c7cc0953 --- /dev/null +++ b/test/battle/ability/sticky_hold.c @@ -0,0 +1,18 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Sticky Hold prevents item theft") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_THIEF, MOVE_EFFECT_STEAL_ITEM)); + PLAYER(SPECIES_URSALUNA) { Item(ITEM_NONE); } + OPPONENT(SPECIES_GASTRODON) { Ability(ABILITY_STICKY_HOLD); Item(ITEM_LIFE_ORB); } + } WHEN { + TURN { MOVE(player, MOVE_THIEF); } + } SCENE { + MESSAGE("Ursaluna used Thief!"); + ABILITY_POPUP(opponent, ABILITY_STICKY_HOLD); + MESSAGE("Foe Gastrodon's Sticky Hold made Thief ineffective!"); + } +} + diff --git a/test/battle/ability/storm_drain.c b/test/battle/ability/storm_drain.c new file mode 100644 index 000000000..38d4a9be7 --- /dev/null +++ b/test/battle/ability/storm_drain.c @@ -0,0 +1,73 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Storm Drain absorbs Water-type moves and increases the Sp. Attack [Gen5+]") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_WATER_GUN].type == TYPE_WATER); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GASTRODON_EAST_SEA) { Ability(ABILITY_STORM_DRAIN); } + } WHEN { + TURN { MOVE(player, MOVE_WATER_GUN); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, player); + HP_BAR(opponent); + }; + ABILITY_POPUP(opponent, ABILITY_STORM_DRAIN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Gastrodon's Sp. Atk rose!"); + } else { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_STORM_DRAIN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Gastrodon's Sp. Atk rose!"); + }; + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, player); + HP_BAR(opponent); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +DOUBLE_BATTLE_TEST("Storm Drain forces single-target Water-type moves to target the Pokémon with this Ability.") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_WATER_GUN].type == TYPE_WATER); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GASTRODON_EAST_SEA) { Ability(ABILITY_STORM_DRAIN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_WATER_GUN, target: opponentRight); + MOVE(playerRight, MOVE_WATER_GUN, target: opponentRight); + MOVE(opponentLeft, MOVE_CELEBRATE); + MOVE(opponentRight, MOVE_CELEBRATE); + } + } SCENE { + if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) { + NONE_OF { + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + }; + ABILITY_POPUP(opponentLeft, ABILITY_STORM_DRAIN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Foe Gastrodon's Sp. Atk rose!"); + ABILITY_POPUP(opponentLeft, ABILITY_STORM_DRAIN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Foe Gastrodon's Sp. Atk rose!"); + } else { + NONE_OF { + HP_BAR(opponentRight); + }; + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, playerRight); + HP_BAR(opponentLeft); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + } +} diff --git a/test/battle/ability/supreme_overlord.c b/test/battle/ability/supreme_overlord.c index fcaca9668..dcaaa494a 100644 --- a/test/battle/ability/supreme_overlord.c +++ b/test/battle/ability/supreme_overlord.c @@ -107,9 +107,9 @@ SINGLE_BATTLE_TEST("Supreme Overlord's message displays correctly after all batt ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); // Everyone faints. SEND_IN_MESSAGE("Kingambit"); + MESSAGE("2 sent out Wobbuffet!"); ABILITY_POPUP(player, ABILITY_SUPREME_OVERLORD); MESSAGE("Kingambit gained strength from the fallen!"); - MESSAGE("2 sent out Wobbuffet!"); } } diff --git a/test/battle/ability/switch_in_abilities.c b/test/battle/ability/switch_in_abilities.c new file mode 100644 index 000000000..79cf2b2dc --- /dev/null +++ b/test/battle/ability/switch_in_abilities.c @@ -0,0 +1,128 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Switch-in abilities trigger in Speed Order at the battle's start - Single Battle") +{ + u32 spdPlayer, spdOpponent; + + PARAMETRIZE { spdPlayer = 5; spdOpponent = 1; } + PARAMETRIZE { spdOpponent = 5; spdPlayer = 1; } + + GIVEN { + PLAYER(SPECIES_EKANS) { Speed(spdPlayer); Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_NINETALES) { Speed(spdOpponent); Ability(ABILITY_DROUGHT); } + } WHEN { + TURN { ; } + } SCENE { + if (spdPlayer > spdOpponent) { + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + } else { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + } + } +} + +DOUBLE_BATTLE_TEST("Switch-in abilities trigger in Speed Order at the battle's start - Double Battle") +{ + u32 spdPlayer1, spdPlayer2, spdOpponent1, spdOpponent2; + + PARAMETRIZE { spdPlayer1 = 5; spdPlayer2 = 4; spdOpponent1 = 3; spdOpponent2 = 2; } + PARAMETRIZE { spdPlayer1 = 2; spdPlayer2 = 3; spdOpponent1 = 4; spdOpponent2 = 5; } + PARAMETRIZE { spdPlayer1 = 4; spdPlayer2 = 3; spdOpponent1 = 5; spdOpponent2 = 2; } + + GIVEN { + PLAYER(SPECIES_KYOGRE) { Speed(spdPlayer1); Ability(ABILITY_DRIZZLE); } + PLAYER(SPECIES_GYARADOS) { Speed(spdPlayer2); Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_PORYGON2) { Speed(spdOpponent1); Ability(ABILITY_DOWNLOAD); } + OPPONENT(SPECIES_PINSIR) { Speed(spdOpponent2); Ability(ABILITY_MOLD_BREAKER); } + } WHEN { + TURN { ; } + } SCENE { + if (spdPlayer1 == 5) { + ABILITY_POPUP(playerLeft, ABILITY_DRIZZLE); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentLeft, ABILITY_DOWNLOAD); + ABILITY_POPUP(opponentRight, ABILITY_MOLD_BREAKER); + } else if (spdOpponent2 == 5) { + ABILITY_POPUP(opponentRight, ABILITY_MOLD_BREAKER); + ABILITY_POPUP(opponentLeft, ABILITY_DOWNLOAD); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(playerLeft, ABILITY_DRIZZLE); + } else { + ABILITY_POPUP(opponentLeft, ABILITY_DOWNLOAD); + ABILITY_POPUP(playerLeft, ABILITY_DRIZZLE); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentRight, ABILITY_MOLD_BREAKER); + } + } +} + +SINGLE_BATTLE_TEST("Switch-in abilities trigger in Speed Order after post-KO switch - Single Battle") +{ + u32 spdPlayer, spdOpponent; + + PARAMETRIZE { spdPlayer = 5; spdOpponent = 1; } + PARAMETRIZE { spdOpponent = 5; spdPlayer = 1; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + PLAYER(SPECIES_EKANS) { Speed(spdPlayer); Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + OPPONENT(SPECIES_PORYGON2) { Speed(spdOpponent); Ability(ABILITY_DOWNLOAD); } + } WHEN { + TURN { MOVE(player, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + TURN { ; } + } SCENE { + MESSAGE("Wobbuffet used Explosion!"); + if (spdPlayer > spdOpponent) { + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponent, ABILITY_DOWNLOAD); + } else { + ABILITY_POPUP(opponent, ABILITY_DOWNLOAD); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + } + } +} + +DOUBLE_BATTLE_TEST("Switch-in abilities trigger in Speed Order after post-KO switch - Double Battle") +{ + u32 spdPlayer1, spdPlayer2, spdOpponent1, spdOpponent2; + + PARAMETRIZE { spdPlayer1 = 5; spdPlayer2 = 4; spdOpponent1 = 3; spdOpponent2 = 2; } + PARAMETRIZE { spdPlayer1 = 2; spdPlayer2 = 3; spdOpponent1 = 4; spdOpponent2 = 5; } + PARAMETRIZE { spdPlayer1 = 4; spdPlayer2 = 3; spdOpponent1 = 5; spdOpponent2 = 2; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + PLAYER(SPECIES_TYRANITAR) { Speed(spdPlayer1); Ability(ABILITY_SAND_STREAM); } + PLAYER(SPECIES_GYARADOS) { Speed(spdPlayer2); Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + OPPONENT(SPECIES_WEEZING_GALARIAN) { Speed(spdOpponent1); Ability(ABILITY_MISTY_SURGE); } + OPPONENT(SPECIES_VULPIX_ALOLAN) { Speed(spdOpponent2); Ability(ABILITY_SNOW_WARNING); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 2); SEND_OUT(playerRight, 3); SEND_OUT(opponentRight, 3); } + TURN { ; } + } SCENE { + MESSAGE("Wobbuffet used Explosion!"); + if (spdPlayer1 == 5) { + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + } else if (spdOpponent2 == 5) { + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + } else { + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + } + } +} diff --git a/test/battle/ability/sword_of_ruin.c b/test/battle/ability/sword_of_ruin.c index ae3e4c73a..02eec69ed 100644 --- a/test/battle/ability/sword_of_ruin.c +++ b/test/battle/ability/sword_of_ruin.c @@ -46,9 +46,9 @@ SINGLE_BATTLE_TEST("Sword of Ruin's message displays correctly after all battler ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); // Everyone faints. MESSAGE("Go! Chien-Pao!"); + MESSAGE("2 sent out Wobbuffet!"); ABILITY_POPUP(player, ABILITY_SWORD_OF_RUIN); MESSAGE("Chien-Pao's Sword of Ruin weakened the Defense of all surrounding Pokémon!"); - MESSAGE("2 sent out Wobbuffet!"); } } diff --git a/test/battle/ability/tablets_of_ruin.c b/test/battle/ability/tablets_of_ruin.c index c8d24f5d9..7862a0bb8 100644 --- a/test/battle/ability/tablets_of_ruin.c +++ b/test/battle/ability/tablets_of_ruin.c @@ -46,9 +46,9 @@ SINGLE_BATTLE_TEST("Tablets of Ruin's message displays correctly after all battl ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); // Everyone faints. MESSAGE("Go! Wo-Chien!"); + MESSAGE("2 sent out Wobbuffet!"); ABILITY_POPUP(player, ABILITY_TABLETS_OF_RUIN); MESSAGE("Wo-Chien's Tablets of Ruin weakened the Attack of all surrounding Pokémon!"); - MESSAGE("2 sent out Wobbuffet!"); } } diff --git a/test/battle/ability/teraform_zero.c b/test/battle/ability/teraform_zero.c new file mode 100644 index 000000000..654051532 --- /dev/null +++ b/test/battle/ability/teraform_zero.c @@ -0,0 +1,102 @@ +#include "global.h" +#include "test/battle.h" + +DOUBLE_BATTLE_TEST("Teraform Zero clears weather and terrain upon activation") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KYOGRE) {Ability(ABILITY_DRIZZLE); } + OPPONENT(SPECIES_TAPU_KOKO) {Ability(ABILITY_ELECTRIC_SURGE); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_TERAFORM_ZERO); + MESSAGE("The rain stopped."); + MESSAGE("The electricity disappeared from the battlefield."); + } +} + +DOUBLE_BATTLE_TEST("Teraform Zero can be supressed") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WEEZING) {Ability(ABILITY_NEUTRALIZING_GAS); } + OPPONENT(SPECIES_KYOGRE) {Ability(ABILITY_DRIZZLE); } + OPPONENT(SPECIES_TAPU_KOKO) {Ability(ABILITY_ELECTRIC_SURGE); } + } WHEN { + TURN { SWITCH(playerRight, 2); MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + } SCENE { + ABILITY_POPUP(playerRight, ABILITY_NEUTRALIZING_GAS); + NONE_OF { + MESSAGE("The rain stopped."); + MESSAGE("The electricity disappeared from the battlefield."); + } + } +} + +SINGLE_BATTLE_TEST("Teraform Zero can be replaced") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_WORRY_SEED].effect == EFFECT_WORRY_SEED); + ASSUME(gMovesInfo[MOVE_REST].effect == EFFECT_REST); + PLAYER(SPECIES_TERAPAGOS); + OPPONENT(SPECIES_WHIMSICOTT) { Ability(ABILITY_PRANKSTER); } + } WHEN { + TURN { MOVE(opponent, MOVE_WORRY_SEED); MOVE(player, MOVE_REST, gimmick: GIMMICK_TERA); } + } SCENE { + MESSAGE("Foe Whimsicott used Worry Seed!"); + MESSAGE("Terapagos acquired Insomnia!"); + MESSAGE("Terapagos used Rest!"); + ABILITY_POPUP(player, ABILITY_INSOMNIA); + MESSAGE("Terapagos stayed awake using its Insomnia!"); + } +} + +SINGLE_BATTLE_TEST("Teraform Zero cannot be swapped") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_SKILL_SWAP].effect == EFFECT_SKILL_SWAP); + PLAYER(SPECIES_TERAPAGOS); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_SKILL_SWAP); } + } SCENE { + MESSAGE("Foe Wobbuffet used Skill Swap!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Teraform Zero cannot be copied") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_ROLE_PLAY].effect == EFFECT_ROLE_PLAY); + PLAYER(SPECIES_TERAPAGOS); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_ROLE_PLAY); } + } SCENE { + MESSAGE("Foe Wobbuffet used Role Play!"); + MESSAGE("But it failed!"); + } +} + +DOUBLE_BATTLE_TEST("Teraform Zero shouldn't cause Neutralizing Gas to show it's popup when trying to activate") +{ + KNOWN_FAILING; // #5010 + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL); + PLAYER(SPECIES_ABSOL) {Ability(ABILITY_PRESSURE); } + PLAYER(SPECIES_WEEZING) {Ability(ABILITY_NEUTRALIZING_GAS); } + OPPONENT(SPECIES_KYOGRE) {Ability(ABILITY_DRIZZLE); } + OPPONENT(SPECIES_TAPU_KOKO) {Ability(ABILITY_ELECTRIC_SURGE); } + } WHEN { + TURN { SWITCH(playerRight, 2); MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + } SCENE { + MESSAGE("Terapagos is storing energy!"); + MESSAGE("Terapagos terastalized into the Stellar type!"); + NOT ABILITY_POPUP(playerRight, ABILITY_NEUTRALIZING_GAS); + MESSAGE("Terapagos used Celebreate!"); + } +} diff --git a/test/battle/ability/trace.c b/test/battle/ability/trace.c index bb72f3886..acc49bcf1 100644 --- a/test/battle/ability/trace.c +++ b/test/battle/ability/trace.c @@ -79,3 +79,20 @@ SINGLE_BATTLE_TEST("Trace will copy an opponent's ability whenever it has the ch MESSAGE("Ralts TRACED Foe Torchic's Blaze!"); } } + +DOUBLE_BATTLE_TEST("Trace respects the turn order") +{ + GIVEN { + PLAYER(SPECIES_DEOXYS_SPEED) { Speed(40); Ability(ABILITY_PRESSURE); } + PLAYER(SPECIES_GARDEVOIR) { Speed(20); Ability(ABILITY_TRACE); } + OPPONENT(SPECIES_HIPPOWDON) { Speed(10); Ability(ABILITY_SAND_STREAM); } + OPPONENT(SPECIES_DEOXYS_SPEED) { Speed(30); Ability(ABILITY_PRESSURE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_PRESSURE); + ABILITY_POPUP(opponentRight, ABILITY_PRESSURE); + ABILITY_POPUP(playerRight, ABILITY_TRACE); + ABILITY_POPUP(opponentLeft, ABILITY_SAND_STREAM); + } +} diff --git a/test/battle/ability/vessel_of_ruin.c b/test/battle/ability/vessel_of_ruin.c index 723c83bdf..362b44c00 100644 --- a/test/battle/ability/vessel_of_ruin.c +++ b/test/battle/ability/vessel_of_ruin.c @@ -46,9 +46,9 @@ SINGLE_BATTLE_TEST("Vessel of Ruin's message displays correctly after all battle ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); // Everyone faints. MESSAGE("Go! Ting-Lu!"); + MESSAGE("2 sent out Wobbuffet!"); ABILITY_POPUP(player, ABILITY_VESSEL_OF_RUIN); MESSAGE("Ting-Lu's Vessel of Ruin weakened the Sp. Atk of all surrounding Pokémon!"); - MESSAGE("2 sent out Wobbuffet!"); } } diff --git a/test/battle/ability/white_smoke.c b/test/battle/ability/white_smoke.c new file mode 100644 index 000000000..4cb168714 --- /dev/null +++ b/test/battle/ability/white_smoke.c @@ -0,0 +1,4 @@ +#include "global.h" +#include "test/battle.h" + +// Tests for White Smoke are handled in test/battle/ability/clear_body.c diff --git a/test/battle/ability/zero_to_hero.c b/test/battle/ability/zero_to_hero.c index 1fd8e9c7e..be72a55a7 100644 --- a/test/battle/ability/zero_to_hero.c +++ b/test/battle/ability/zero_to_hero.c @@ -153,9 +153,9 @@ SINGLE_BATTLE_TEST("Zero to Hero's message displays correctly after all battlers ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); // Everyone faints. SEND_IN_MESSAGE("Palafin"); + MESSAGE("2 sent out Wobbuffet!"); ABILITY_POPUP(player, ABILITY_ZERO_TO_HERO); MESSAGE("Palafin underwent a heroic transformation!"); - MESSAGE("2 sent out Wobbuffet!"); } } diff --git a/test/battle/ai.c b/test/battle/ai/ai.c similarity index 99% rename from test/battle/ai.c rename to test/battle/ai/ai.c index 48eee5c05..a19481c7e 100644 --- a/test/battle/ai.c +++ b/test/battle/ai/ai.c @@ -402,9 +402,9 @@ AI_DOUBLE_BATTLE_TEST("AI will not use Helping Hand if partner does not have any { 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; } + 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); @@ -431,7 +431,7 @@ AI_DOUBLE_BATTLE_TEST("AI will not use a status move if partner already chose He for (j = MOVE_NONE + 1; j < MOVES_COUNT; j++) { if (gMovesInfo[j].category == DAMAGE_CATEGORY_STATUS) { - PARAMETRIZE{ statusMove = j; } + PARAMETRIZE { statusMove = j; } } } diff --git a/test/battle/ai_calc_best_move_score.c b/test/battle/ai/ai_calc_best_move_score.c similarity index 100% rename from test/battle/ai_calc_best_move_score.c rename to test/battle/ai/ai_calc_best_move_score.c diff --git a/test/battle/ai_check_viability.c b/test/battle/ai/ai_check_viability.c similarity index 100% rename from test/battle/ai_check_viability.c rename to test/battle/ai/ai_check_viability.c diff --git a/test/battle/ai_choice.c b/test/battle/ai/ai_choice.c similarity index 75% rename from test/battle/ai_choice.c rename to test/battle/ai/ai_choice.c index a589a24d7..c9992d2ca 100644 --- a/test/battle/ai_choice.c +++ b/test/battle/ai/ai_choice.c @@ -20,8 +20,8 @@ AI_SINGLE_BATTLE_TEST("Choiced Pokémon switch out after using a status move onc for (j = 0; j < ARRAY_COUNT(choiceItems); j++) { - PARAMETRIZE{ ability = ABILITY_NONE; heldItem = choiceItems[j]; } - PARAMETRIZE{ ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; } + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; } } GIVEN { @@ -55,8 +55,8 @@ AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use stat boosting moves") for (j = 0; j < ARRAY_COUNT(choiceItems); j++) { - PARAMETRIZE{ ability = ABILITY_NONE; heldItem = choiceItems[j]; } - PARAMETRIZE{ ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; } + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; } } GIVEN { @@ -87,10 +87,10 @@ AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use status move if they are the on for (j = 0; j < ARRAY_COUNT(choiceItems); j++) { - PARAMETRIZE{ ability = ABILITY_NONE; heldItem = choiceItems[j]; isAlive = 0; } - PARAMETRIZE{ ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; isAlive = 0; } - PARAMETRIZE{ ability = ABILITY_NONE; heldItem = choiceItems[j]; isAlive = 1; } - PARAMETRIZE{ ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; isAlive = 1; } + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; isAlive = 0; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; isAlive = 0; } + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; isAlive = 1; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; isAlive = 1; } } GIVEN { @@ -122,10 +122,10 @@ AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use status move if they don't have for (j = 0; j < ARRAY_COUNT(choiceItems); j++) { - PARAMETRIZE{ ability = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_SWAMPERT; move = MOVE_WATERFALL; } - PARAMETRIZE{ ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_SWAMPERT; move = MOVE_WATERFALL; } - PARAMETRIZE{ ability = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_ELEKID; move = MOVE_THUNDER_WAVE; } - PARAMETRIZE{ ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_ELEKID; move = MOVE_THUNDER_WAVE; } + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_SWAMPERT; move = MOVE_WATERFALL; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_SWAMPERT; move = MOVE_WATERFALL; } + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_ELEKID; move = MOVE_THUNDER_WAVE; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_ELEKID; move = MOVE_THUNDER_WAVE; } } GIVEN { @@ -157,10 +157,10 @@ AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use status move if they are trappe for (j = 0; j < ARRAY_COUNT(choiceItems); j++) { - PARAMETRIZE{ aiAbility = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_RHYDON; playerAbility = ABILITY_LIGHTNING_ROD; } - PARAMETRIZE{ aiAbility = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_RHYDON; playerAbility = ABILITY_LIGHTNING_ROD; } - PARAMETRIZE{ aiAbility = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_DUGTRIO; playerAbility = ABILITY_ARENA_TRAP; } - PARAMETRIZE{ aiAbility = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_DUGTRIO; playerAbility = ABILITY_ARENA_TRAP; } + PARAMETRIZE { aiAbility = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_RHYDON; playerAbility = ABILITY_LIGHTNING_ROD; } + PARAMETRIZE { aiAbility = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_RHYDON; playerAbility = ABILITY_LIGHTNING_ROD; } + PARAMETRIZE { aiAbility = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_DUGTRIO; playerAbility = ABILITY_ARENA_TRAP; } + PARAMETRIZE { aiAbility = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_DUGTRIO; playerAbility = ABILITY_ARENA_TRAP; } } GIVEN { diff --git a/test/battle/ai_flag_risky.c b/test/battle/ai/ai_flag_risky.c similarity index 89% rename from test/battle/ai_flag_risky.c rename to test/battle/ai/ai_flag_risky.c index 4c5d89205..e6156de5a 100644 --- a/test/battle/ai_flag_risky.c +++ b/test/battle/ai/ai_flag_risky.c @@ -5,8 +5,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will blindly Mirror Coat against specia { u32 aiRiskyFlag = 0; - PARAMETRIZE{ aiRiskyFlag = 0; } - PARAMETRIZE{ aiRiskyFlag = AI_FLAG_RISKY; } + PARAMETRIZE { aiRiskyFlag = 0; } + PARAMETRIZE { aiRiskyFlag = AI_FLAG_RISKY; } GIVEN { ASSUME(gMovesInfo[MOVE_MIRROR_COAT].effect == EFFECT_MIRROR_COAT); @@ -24,8 +24,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will blindly Counter against physical a { u32 aiRiskyFlag = 0; - PARAMETRIZE{ aiRiskyFlag = 0; } - PARAMETRIZE{ aiRiskyFlag = AI_FLAG_RISKY; } + PARAMETRIZE { aiRiskyFlag = 0; } + PARAMETRIZE { aiRiskyFlag = AI_FLAG_RISKY; } GIVEN { ASSUME(gMovesInfo[MOVE_COUNTER].effect == EFFECT_COUNTER); @@ -43,8 +43,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will prioritize Revenge if slower") { u32 aiRiskyFlag = 0; - PARAMETRIZE{ aiRiskyFlag = 0; } - PARAMETRIZE{ aiRiskyFlag = AI_FLAG_RISKY; } + PARAMETRIZE { aiRiskyFlag = 0; } + PARAMETRIZE { aiRiskyFlag = AI_FLAG_RISKY; } GIVEN { ASSUME(gMovesInfo[MOVE_REVENGE].effect == EFFECT_REVENGE); @@ -60,8 +60,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: Mid-battle switches prioritize offensive o { u32 aiRiskyFlag = 0; - PARAMETRIZE{ aiRiskyFlag = 0; } - PARAMETRIZE{ aiRiskyFlag = AI_FLAG_RISKY; } + PARAMETRIZE { aiRiskyFlag = 0; } + PARAMETRIZE { aiRiskyFlag = AI_FLAG_RISKY; } GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | aiRiskyFlag); @@ -78,8 +78,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI prefers high damage moves at the expens { u32 aiRiskyFlag = 0; - PARAMETRIZE{ aiRiskyFlag = 0; } - PARAMETRIZE{ aiRiskyFlag = AI_FLAG_RISKY; } + PARAMETRIZE { aiRiskyFlag = 0; } + PARAMETRIZE { aiRiskyFlag = AI_FLAG_RISKY; } GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiRiskyFlag); diff --git a/test/battle/ai/ai_flag_sequence_switching.c b/test/battle/ai/ai_flag_sequence_switching.c new file mode 100644 index 000000000..1b4a264a2 --- /dev/null +++ b/test/battle/ai/ai_flag_sequence_switching.c @@ -0,0 +1,129 @@ +#include "global.h" +#include "test/battle.h" + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: AI will always switch after a KO in exactly party order") +{ + u32 aiSequenceSwitchingFlag = 0; + + PARAMETRIZE { aiSequenceSwitchingFlag = 0; } + PARAMETRIZE { aiSequenceSwitchingFlag = AI_FLAG_SEQUENCE_SWITCHING; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSequenceSwitchingFlag); + PLAYER(SPECIES_SWELLOW) { Level (50); } + OPPONENT(SPECIES_MACHOP) { Level(5); } + OPPONENT(SPECIES_MACHOKE) { Level(5); } + OPPONENT(SPECIES_MACHAMP) { Level(5); } + OPPONENT(SPECIES_MANKEY) { Level(5); } + OPPONENT(SPECIES_PRIMEAPE) { Level(5); } + OPPONENT(SPECIES_MAGNEZONE) { Level(100); } + } WHEN { + TURN { MOVE(player, MOVE_WING_ATTACK); } + if (aiSequenceSwitchingFlag) { + TURN { MOVE(player, MOVE_WING_ATTACK); } + TURN { MOVE(player, MOVE_WING_ATTACK); } + TURN { MOVE(player, MOVE_WING_ATTACK); } + TURN { MOVE(player, MOVE_WING_ATTACK); } + } + } SCENE { + if (aiSequenceSwitchingFlag) { + MESSAGE("{PKMN} TRAINER LEAF sent out Machoke!"); + MESSAGE("{PKMN} TRAINER LEAF sent out Machamp!"); + MESSAGE("{PKMN} TRAINER LEAF sent out Mankey!"); + MESSAGE("{PKMN} TRAINER LEAF sent out Primeape!"); + MESSAGE("{PKMN} TRAINER LEAF sent out Magnezone!"); + } + else { + MESSAGE("{PKMN} TRAINER LEAF sent out Magnezone!"); + } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: Roar and Dragon Tail still force switch to random party member") +{ + u32 move; + + PARAMETRIZE { move = MOVE_ROAR; } + PARAMETRIZE {move = MOVE_DRAGON_TAIL; } + + PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH); + GIVEN { + ASSUME(gMovesInfo[MOVE_ROAR].effect == EFFECT_ROAR); + ASSUME(gMovesInfo[MOVE_DRAGON_TAIL].effect == EFFECT_HIT_SWITCH_TARGET); + AI_FLAGS(AI_FLAG_SEQUENCE_SWITCHING); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + MESSAGE("Foe Bulbasaur was dragged out!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: AI will always switch into lowest party index after U-Turn, Parting Shot, and Baton Pass") +{ + u32 j, aiSequenceSwitchingFlag = 0, move = MOVE_NONE; + + static const u32 switchMoves[] = { + MOVE_U_TURN, + MOVE_PARTING_SHOT, + MOVE_BATON_PASS, + }; + + for (j = 0; j < ARRAY_COUNT(switchMoves); j++) + { + PARAMETRIZE { aiSequenceSwitchingFlag = 0; move = switchMoves[j]; } + PARAMETRIZE { aiSequenceSwitchingFlag = AI_FLAG_SEQUENCE_SWITCHING; move = switchMoves[j]; } + } + + GIVEN { + ASSUME(gMovesInfo[MOVE_U_TURN].effect == EFFECT_HIT_ESCAPE); + ASSUME(gMovesInfo[MOVE_PARTING_SHOT].effect == EFFECT_PARTING_SHOT); + ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSequenceSwitchingFlag); + PLAYER(SPECIES_SWELLOW) { Level (50); } + OPPONENT(SPECIES_MACHOP) { Level(1); Moves(move); } + OPPONENT(SPECIES_MACHOP) { Level(2); Moves(move); } + OPPONENT(SPECIES_MACHOP) { Level(3); Moves(move); } + OPPONENT(SPECIES_MACHOP) { Level(4); Moves(move); } + OPPONENT(SPECIES_MACHOP) { Level(5); Moves(move); } + OPPONENT(SPECIES_MAGNEZONE) { Level(100); Moves(move); } + } WHEN { + if (aiSequenceSwitchingFlag) { + TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 1); } + TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 0); } + TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 1); } + TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 0); } + TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 1); } + } + else { + TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 5); } + } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: AI will not switch mid-battle") +{ + u32 aiSequenceSwitchingFlag = 0; + + PARAMETRIZE { aiSequenceSwitchingFlag = 0; } + PARAMETRIZE { aiSequenceSwitchingFlag = AI_FLAG_SEQUENCE_SWITCHING; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSequenceSwitchingFlag); + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_LICK); } + OPPONENT(SPECIES_GASTLY) { Moves(MOVE_SHADOW_BALL); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_KARATE_CHOP); } + } WHEN { + if (aiSequenceSwitchingFlag == AI_FLAG_SEQUENCE_SWITCHING) { + TURN { MOVE(player, MOVE_LICK) ; EXPECT_MOVE(opponent, MOVE_SHADOW_BALL); } + } + else { + TURN { MOVE(player, MOVE_LICK) ; EXPECT_SWITCH(opponent, 1); } + } + } +} diff --git a/test/battle/ai_powerful_status.c b/test/battle/ai/ai_powerful_status.c similarity index 100% rename from test/battle/ai_powerful_status.c rename to test/battle/ai/ai_powerful_status.c diff --git a/test/battle/ai_switching.c b/test/battle/ai/ai_switching.c similarity index 89% rename from test/battle/ai_switching.c rename to test/battle/ai/ai_switching.c index 50b59f66d..fcc8e322c 100644 --- a/test/battle/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -67,6 +67,19 @@ AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spot } } +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: U-Turn will send out Ace Mon if it's the only one remaining") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_U_TURN].effect == EFFECT_HIT_ESCAPE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_ACE_POKEMON); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_U_TURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_U_TURN); EXPECT_SEND_OUT(opponent, 1); } + } +} + // General AI_FLAG_SMART_MON_CHOICES behaviour AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Number of hits to KO calculation checks whether incoming damage is less than recurring healing to avoid an infinite loop") { @@ -127,9 +140,9 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will not switch in a Pokemo u32 speedAlakazm; u32 aiSmartSwitchFlags = 0; - PARAMETRIZE{ speedAlakazm = 200; alakazamFirst = TRUE; } // AI will always send out Alakazan as it sees a KO with Focus Blast, even if Alakazam dies before it can get it off - PARAMETRIZE{ speedAlakazm = 200; alakazamFirst = FALSE; aiSmartSwitchFlags = AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES lets AI see that Alakazam would be KO'd before it can KO, and won't switch it in - PARAMETRIZE{ speedAlakazm = 400; alakazamFirst = TRUE; aiSmartSwitchFlags = AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES recognizes that Alakazam is faster and can KO, and will switch it in + PARAMETRIZE { speedAlakazm = 200; alakazamFirst = TRUE; } // AI will always send out Alakazan as it sees a KO with Focus Blast, even if Alakazam dies before it can get it off + PARAMETRIZE { speedAlakazm = 200; alakazamFirst = FALSE; aiSmartSwitchFlags = AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES lets AI see that Alakazam would be KO'd before it can KO, and won't switch it in + PARAMETRIZE { speedAlakazm = 400; alakazamFirst = TRUE; aiSmartSwitchFlags = AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES recognizes that Alakazam is faster and can KO, and will switch it in GIVEN { ASSUME(gMovesInfo[MOVE_PSYCHIC].category == DAMAGE_CATEGORY_SPECIAL); @@ -159,8 +172,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI considers hazard damage whe u32 aiIsSmart = 0; u32 aiSmartSwitchFlags = 0; - PARAMETRIZE{ aiIsSmart = 0; aiSmartSwitchFlags = 0; } // AI doesn't care about hazard damage resulting in Pokemon being KO'd - PARAMETRIZE{ aiIsSmart = 1; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES avoids being KO'd as a result of hazards damage + PARAMETRIZE { aiIsSmart = 0; aiSmartSwitchFlags = 0; } // AI doesn't care about hazard damage resulting in Pokemon being KO'd + PARAMETRIZE { aiIsSmart = 1; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES avoids being KO'd as a result of hazards damage GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSmartSwitchFlags); @@ -181,10 +194,10 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize u32 move2; u32 expectedIndex; - PARAMETRIZE{ expectedIndex = 3; move1 = MOVE_TACKLE; move2 = MOVE_TACKLE; aiSmartSwitchFlags = 0; } // When not smart, AI will only switch in a defensive mon if it has a SE move, otherwise will just default to damage - PARAMETRIZE{ expectedIndex = 1; move1 = MOVE_GIGA_DRAIN; move2 = MOVE_TACKLE; aiSmartSwitchFlags = 0; } - PARAMETRIZE{ expectedIndex = 2; move1 = MOVE_TACKLE; move2 = MOVE_TACKLE; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } // When smart, AI will prioritize SE move, but still switch in good type matchup without SE move - PARAMETRIZE{ expectedIndex = 1; move1 = MOVE_GIGA_DRAIN; move2 = MOVE_TACKLE; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } + PARAMETRIZE { expectedIndex = 3; move1 = MOVE_TACKLE; move2 = MOVE_TACKLE; aiSmartSwitchFlags = 0; } // When not smart, AI will only switch in a defensive mon if it has a SE move, otherwise will just default to damage + PARAMETRIZE { expectedIndex = 1; move1 = MOVE_GIGA_DRAIN; move2 = MOVE_TACKLE; aiSmartSwitchFlags = 0; } + PARAMETRIZE { expectedIndex = 2; move1 = MOVE_TACKLE; move2 = MOVE_TACKLE; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } // When smart, AI will prioritize SE move, but still switch in good type matchup without SE move + PARAMETRIZE { expectedIndex = 1; move1 = MOVE_GIGA_DRAIN; move2 = MOVE_TACKLE; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSmartSwitchFlags); @@ -257,8 +270,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will not switch out if Pokemo { u32 move1; - PARAMETRIZE{ move1 = MOVE_TACKLE; } - PARAMETRIZE{ move1 = MOVE_RAPID_SPIN; } + PARAMETRIZE { move1 = MOVE_TACKLE; } + PARAMETRIZE { move1 = MOVE_RAPID_SPIN; } GIVEN { ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); diff --git a/test/battle/ai_trytofaint.c b/test/battle/ai/ai_trytofaint.c similarity index 100% rename from test/battle/ai_trytofaint.c rename to test/battle/ai/ai_trytofaint.c diff --git a/test/battle/form_change/mega_evolution.c b/test/battle/form_change/mega_evolution.c index 4a2c05305..ffab6af18 100644 --- a/test/battle/form_change/mega_evolution.c +++ b/test/battle/form_change/mega_evolution.c @@ -153,3 +153,26 @@ SINGLE_BATTLE_TEST("Regular Mega Evolution and Fervent Wish Mega Evolution can h EXPECT_EQ(opponent->species, SPECIES_GARDEVOIR_MEGA); } } + +SINGLE_BATTLE_TEST("Mega Evolved Pokemon do not change abilities after fainting") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_CRUNCH].makesContact == TRUE); + ASSUME(gSpeciesInfo[SPECIES_GARCHOMP_MEGA].abilities[0] != ABILITY_ROUGH_SKIN); + ASSUME(gSpeciesInfo[SPECIES_GARCHOMP_MEGA].abilities[1] != ABILITY_ROUGH_SKIN); + ASSUME(gSpeciesInfo[SPECIES_GARCHOMP_MEGA].abilities[2] != ABILITY_ROUGH_SKIN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GARCHOMP) { Ability(ABILITY_ROUGH_SKIN); Item(ITEM_GARCHOMPITE); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_CRUNCH); MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CRUNCH, player); + MESSAGE("Foe Garchomp fainted!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_ROUGH_SKIN); + MESSAGE("Wobbuffet was hurt by Foe Garchomp's Rough Skin!"); + HP_BAR(player); + } + } +} diff --git a/test/battle/form_change/status.c b/test/battle/form_change/status.c index f0d5a9f3f..0b94202bb 100644 --- a/test/battle/form_change/status.c +++ b/test/battle/form_change/status.c @@ -24,7 +24,7 @@ SINGLE_BATTLE_TEST("Shaymin-Sky reverts to Shaymin-Land when frozen or frostbitt } SCENE { ANIMATION(ANIM_TYPE_MOVE, move, opponent); if (move == MOVE_POWDER_SNOW) { - STATUS_ICON(player, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(player, TRUE); NOT HP_BAR(player); // Regression caused by Mimikyu form change MESSAGE("Shaymin transformed!"); } else { diff --git a/test/battle/gimmick/terastal.c b/test/battle/gimmick/terastal.c index b3925fcba..f14c398b6 100644 --- a/test/battle/gimmick/terastal.c +++ b/test/battle/gimmick/terastal.c @@ -762,6 +762,89 @@ SINGLE_BATTLE_TEST("(TERA) Stellar type's one-time boost factors in dynamically- } } +SINGLE_BATTLE_TEST("(TERA) Terapagos retains the Stellar type boost at all times") +{ + s16 damage[2]; + u32 move; + PARAMETRIZE { move = MOVE_TACKLE; } + PARAMETRIZE { move = MOVE_MACH_PUNCH; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].type == TYPE_NORMAL); + ASSUME(gMovesInfo[MOVE_MACH_PUNCH].type != TYPE_NORMAL); + PLAYER(SPECIES_TERAPAGOS); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move, gimmick: GIMMICK_TERA); } + TURN { MOVE(player, move); } + } SCENE { + HP_BAR(opponent, captureDamage: &damage[0]); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terapagos retains its base defensive profile when Terastalizing") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_BRICK_BREAK); } + } SCENE { + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("(TERA) Illusion breaks if the pokemon Terastalizes") +{ + KNOWN_FAILING; // #5015 + u32 species; + PARAMETRIZE { species = SPECIES_TERAPAGOS; } + PARAMETRIZE { species = SPECIES_WOBBUFFET; } + GIVEN { + PLAYER(SPECIES_ZOROARK) { TeraType(TYPE_DARK); } + PLAYER(species); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + } SCENE { + MESSAGE("Zoroark's Illusion wore off!"); + } +} + +/* +// This test freezes the emulator +SINGLE_BATTLE_TEST("(TERA) Transformed pokemon can't Terastalize") +{ + GIVEN { + PLAYER(SPECIES_DITTO); + OPPONENT(SPECIES_TERAPAGOS) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_TRANSFORM); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + } +} +*/ + +SINGLE_BATTLE_TEST("(TERA) Pokemon with Tera forms change upon Terastallizing") +{ + u32 species, targetSpecies; + PARAMETRIZE { species = SPECIES_OGERPON_TEAL_MASK; targetSpecies = SPECIES_OGERPON_TEAL_MASK_TERA; } + PARAMETRIZE { species = SPECIES_OGERPON_WELLSPRING_MASK; targetSpecies = SPECIES_OGERPON_WELLSPRING_MASK_TERA; } + PARAMETRIZE { species = SPECIES_OGERPON_HEARTHFLAME_MASK; targetSpecies = SPECIES_OGERPON_HEARTHFLAME_MASK_TERA; } + PARAMETRIZE { species = SPECIES_OGERPON_CORNERSTONE_MASK; targetSpecies = SPECIES_OGERPON_CORNERSTONE_MASK_TERA; } + PARAMETRIZE { species = SPECIES_TERAPAGOS_TERASTAL; targetSpecies = SPECIES_TERAPAGOS_STELLAR; } + GIVEN { + PLAYER(species); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + } THEN { + EXPECT_EQ(player->species, targetSpecies); + } +} + SINGLE_BATTLE_TEST("(TERA) All type indicators function correctly") { u32 type; @@ -794,20 +877,34 @@ SINGLE_BATTLE_TEST("(TERA) All type indicators function correctly") } } -SINGLE_BATTLE_TEST("(TERA) Pokemon with Tera forms change upon Terastallizing") +SINGLE_BATTLE_TEST("(TERA) All type indicators function correctly - Opponent") { - u32 species, targetSpecies; - PARAMETRIZE { species = SPECIES_OGERPON_TEAL_MASK; targetSpecies = SPECIES_OGERPON_TEAL_MASK_TERA; } - PARAMETRIZE { species = SPECIES_OGERPON_WELLSPRING_MASK; targetSpecies = SPECIES_OGERPON_WELLSPRING_MASK_TERA; } - PARAMETRIZE { species = SPECIES_OGERPON_HEARTHFLAME_MASK; targetSpecies = SPECIES_OGERPON_HEARTHFLAME_MASK_TERA; } - PARAMETRIZE { species = SPECIES_OGERPON_CORNERSTONE_MASK; targetSpecies = SPECIES_OGERPON_CORNERSTONE_MASK_TERA; } - PARAMETRIZE { species = SPECIES_TERAPAGOS_TERASTAL; targetSpecies = SPECIES_TERAPAGOS_STELLAR; } + u32 type; + PARAMETRIZE { type = TYPE_NONE; } + PARAMETRIZE { type = TYPE_NORMAL; } + PARAMETRIZE { type = TYPE_FIGHTING; } + PARAMETRIZE { type = TYPE_FLYING; } + PARAMETRIZE { type = TYPE_POISON; } + PARAMETRIZE { type = TYPE_GROUND; } + PARAMETRIZE { type = TYPE_ROCK; } + PARAMETRIZE { type = TYPE_BUG; } + PARAMETRIZE { type = TYPE_GHOST; } + PARAMETRIZE { type = TYPE_STEEL; } + PARAMETRIZE { type = TYPE_MYSTERY; } + PARAMETRIZE { type = TYPE_FIRE; } + PARAMETRIZE { type = TYPE_WATER; } + PARAMETRIZE { type = TYPE_GRASS; } + PARAMETRIZE { type = TYPE_ELECTRIC; } + PARAMETRIZE { type = TYPE_PSYCHIC; } + PARAMETRIZE { type = TYPE_ICE; } + PARAMETRIZE { type = TYPE_DRAGON; } + PARAMETRIZE { type = TYPE_DARK; } + PARAMETRIZE { type = TYPE_FAIRY; } + PARAMETRIZE { type = TYPE_STELLAR; } GIVEN { - PLAYER(species); - OPPONENT(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { TeraType(type); } } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } - } THEN { - EXPECT_EQ(player->species, targetSpecies); + TURN { MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } } } diff --git a/test/battle/hold_effect/cure_status.c b/test/battle/hold_effect/cure_status.c index 93921ce9c..d00d891e2 100644 --- a/test/battle/hold_effect/cure_status.c +++ b/test/battle/hold_effect/cure_status.c @@ -72,7 +72,7 @@ SINGLE_BATTLE_TEST("Rawst and Lum Berries cure burn") } } -SINGLE_BATTLE_TEST("Aspear and Lum Berries cure freeze") +SINGLE_BATTLE_TEST("Aspear and Lum Berries cure freeze or frostbite") { u16 item; @@ -88,9 +88,9 @@ SINGLE_BATTLE_TEST("Aspear and Lum Berries cure freeze") } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_ICE_PUNCH, player); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRZ, opponent); - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); - STATUS_ICON(opponent, freeze: FALSE); + FREEZE_OR_FROSTBURN_STATUS(opponent, FALSE); } } @@ -163,12 +163,12 @@ SINGLE_BATTLE_TEST("Berry hold effect cures status if a pokemon enters a battle" u16 status; u16 item; - PARAMETRIZE{ status = STATUS1_BURN; item = ITEM_RAWST_BERRY; } - PARAMETRIZE{ status = STATUS1_FREEZE; item = ITEM_ASPEAR_BERRY; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; item = ITEM_CHERI_BERRY; } - PARAMETRIZE{ status = STATUS1_POISON; item = ITEM_PECHA_BERRY; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; item = ITEM_PECHA_BERRY; } - PARAMETRIZE{ status = STATUS1_SLEEP; item = ITEM_CHESTO_BERRY; } + PARAMETRIZE { status = STATUS1_BURN; item = ITEM_RAWST_BERRY; } + PARAMETRIZE { status = STATUS1_FREEZE; item = ITEM_ASPEAR_BERRY; } + PARAMETRIZE { status = STATUS1_PARALYSIS; item = ITEM_CHERI_BERRY; } + PARAMETRIZE { status = STATUS1_POISON; item = ITEM_PECHA_BERRY; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; item = ITEM_PECHA_BERRY; } + PARAMETRIZE { status = STATUS1_SLEEP; item = ITEM_CHESTO_BERRY; } GIVEN { ASSUME(gItemsInfo[ITEM_RAWST_BERRY].holdEffect == HOLD_EFFECT_CURE_BRN); diff --git a/test/battle/hold_effect/mirror_herb.c b/test/battle/hold_effect/mirror_herb.c index 135ec270b..52326cc60 100644 --- a/test/battle/hold_effect/mirror_herb.c +++ b/test/battle/hold_effect/mirror_herb.c @@ -73,3 +73,18 @@ DOUBLE_BATTLE_TEST("Mirror Herb does not trigger for Ally's Soul Heart's stat ra EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); } } + +SINGLE_BATTLE_TEST("Mirror Herb copies the boost gained by an ability") +{ + GIVEN { + PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_MIRROR_HERB); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_INTREPID_SWORD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} diff --git a/test/battle/item_effect/cure_status.c b/test/battle/item_effect/cure_status.c index f73bbae32..6c7cc2e64 100644 --- a/test/battle/item_effect/cure_status.c +++ b/test/battle/item_effect/cure_status.c @@ -112,12 +112,12 @@ SINGLE_BATTLE_TEST("Ice Heal heals a battler from being frozen") SINGLE_BATTLE_TEST("Full Heal heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_FULL_HEAL].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -134,12 +134,12 @@ SINGLE_BATTLE_TEST("Full Heal heals a battler from any primary status") SINGLE_BATTLE_TEST("Heal Powder heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_HEAL_POWDER].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -156,12 +156,12 @@ SINGLE_BATTLE_TEST("Heal Powder heals a battler from any primary status") SINGLE_BATTLE_TEST("Pewter Crunchies heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_PEWTER_CRUNCHIES].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -178,12 +178,12 @@ SINGLE_BATTLE_TEST("Pewter Crunchies heals a battler from any primary status") SINGLE_BATTLE_TEST("Lava Cookies heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_LAVA_COOKIE].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -200,12 +200,12 @@ SINGLE_BATTLE_TEST("Lava Cookies heals a battler from any primary status") SINGLE_BATTLE_TEST("Rage Candy Bar heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_RAGE_CANDY_BAR].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -222,12 +222,12 @@ SINGLE_BATTLE_TEST("Rage Candy Bar heals a battler from any primary status") SINGLE_BATTLE_TEST("Old Gateu heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_OLD_GATEAU].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -244,12 +244,12 @@ SINGLE_BATTLE_TEST("Old Gateu heals a battler from any primary status") SINGLE_BATTLE_TEST("Casteliacone heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_CASTELIACONE].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -266,12 +266,12 @@ SINGLE_BATTLE_TEST("Casteliacone heals a battler from any primary status") SINGLE_BATTLE_TEST("Lumiose Galette heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_LUMIOSE_GALETTE].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -288,12 +288,12 @@ SINGLE_BATTLE_TEST("Lumiose Galette heals a battler from any primary status") SINGLE_BATTLE_TEST("Shalour Sable heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_SHALOUR_SABLE].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -310,12 +310,12 @@ SINGLE_BATTLE_TEST("Shalour Sable heals a battler from any primary status") SINGLE_BATTLE_TEST("Big Malasada heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_BIG_MALASADA].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } diff --git a/test/battle/item_effect/heal_and_cure_status.c b/test/battle/item_effect/heal_and_cure_status.c index 80719cf23..70dc8322d 100644 --- a/test/battle/item_effect/heal_and_cure_status.c +++ b/test/battle/item_effect/heal_and_cure_status.c @@ -9,13 +9,13 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Full Restore restores a battler's HP and cures any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } - PARAMETRIZE{ status = STATUS1_NONE; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_NONE; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(300); Status1(status); } OPPONENT(SPECIES_WOBBUFFET); @@ -35,13 +35,13 @@ SINGLE_BATTLE_TEST("Full Restore restores a battler's HP and cures any primary s SINGLE_BATTLE_TEST("Full Restore restores a party members HP and cures any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } - PARAMETRIZE{ status = STATUS1_NONE; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_NONE; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(300); Status1(status); } PLAYER(SPECIES_WYNAUT) { HP(1); MaxHP(300); Status1(status); } @@ -64,12 +64,12 @@ SINGLE_BATTLE_TEST("Full Restore restores a party members HP and cures any prima SINGLE_BATTLE_TEST("Full Restore heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { Status1(status); } OPPONENT(SPECIES_WYNAUT); @@ -86,12 +86,12 @@ SINGLE_BATTLE_TEST("Full Restore heals a battler from any primary status") SINGLE_BATTLE_TEST("Full Restore heals a party member from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WYNAUT) { Status1(status); } diff --git a/test/battle/move_effect/acrobatics.c b/test/battle/move_effect/acrobatics.c index bc34719a4..70953d095 100644 --- a/test/battle/move_effect/acrobatics.c +++ b/test/battle/move_effect/acrobatics.c @@ -1,5 +1,48 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Acrobatics doubles in power if the user has no held item"); -TO_DO_BATTLE_TEST("Acrobatics still doubles in power when Flying Gem is consumed"); +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_ACROBATICS].effect == EFFECT_ACROBATICS); + ASSUME(gMovesInfo[MOVE_ACROBATICS].type == TYPE_FLYING); +} + +SINGLE_BATTLE_TEST("Acrobatics doubles in power if the user has no held item", s16 damage) +{ + u16 heldItem; + PARAMETRIZE { heldItem = ITEM_POTION; } + PARAMETRIZE { heldItem = ITEM_NONE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(heldItem); } + } WHEN { + TURN { MOVE(opponent, MOVE_ACROBATICS); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Acrobatics still doubles in power when Flying Gem is consumed", s16 damage) +{ + u16 heldItem; + PARAMETRIZE { heldItem = ITEM_NONE; } + PARAMETRIZE { heldItem = ITEM_FLYING_GEM; } + GIVEN { + ASSUME(I_GEM_BOOST_POWER >= GEN_6); + ASSUME(gItemsInfo[ITEM_FLYING_GEM].holdEffect == HOLD_EFFECT_GEMS); + ASSUME(gItemsInfo[ITEM_FLYING_GEM].secondaryId == TYPE_FLYING); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(heldItem); } + } WHEN { + TURN { MOVE(opponent, MOVE_ACROBATICS); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + if (I_GEM_BOOST_POWER >= GEN_6) + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.3), (results[1].damage)); + else + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), (results[1].damage)); + } +} diff --git a/test/battle/move_effect/attack_accuracy_up.c b/test/battle/move_effect/attack_accuracy_up.c index 025a0cb86..102f4d4d2 100644 --- a/test/battle/move_effect/attack_accuracy_up.c +++ b/test/battle/move_effect/attack_accuracy_up.c @@ -1,4 +1,18 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Hone Claws increases Attack and Accuracy by one stage each"); +SINGLE_BATTLE_TEST("Hone Claws increases Attack and Accuracy by one stage each") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_HONE_CLAWS].effect == EFFECT_ATTACK_ACCURACY_UP); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HONE_CLAWS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HONE_CLAWS, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Attack rose!"); + MESSAGE("Wobbuffet's accuracy rose!"); + } +} diff --git a/test/battle/move_effect/aura_wheel.c b/test/battle/move_effect/aura_wheel.c index fa4190dbd..3d601f358 100644 --- a/test/battle/move_effect/aura_wheel.c +++ b/test/battle/move_effect/aura_wheel.c @@ -10,8 +10,8 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Aura Wheel raises Speed; fails if the user is not Morpeko") { u16 species; - PARAMETRIZE{ species = SPECIES_WOBBUFFET; } - PARAMETRIZE{ species = SPECIES_MORPEKO; } + PARAMETRIZE { species = SPECIES_WOBBUFFET; } + PARAMETRIZE { species = SPECIES_MORPEKO; } GIVEN { PLAYER(species); OPPONENT(SPECIES_WOBBUFFET); diff --git a/test/battle/move_effect/embargo.c b/test/battle/move_effect/embargo.c index f1afba711..11e4069d8 100644 --- a/test/battle/move_effect/embargo.c +++ b/test/battle/move_effect/embargo.c @@ -253,8 +253,8 @@ SINGLE_BATTLE_TEST("Embargo disables the effect of the Plate items on the move J { u32 heldItem; - PARAMETRIZE{ heldItem = ITEM_NONE; } - PARAMETRIZE{ heldItem = ITEM_PIXIE_PLATE; } + PARAMETRIZE { heldItem = ITEM_NONE; } + PARAMETRIZE { heldItem = ITEM_PIXIE_PLATE; } GIVEN { PLAYER(SPECIES_ARCEUS) { Item(heldItem); }; OPPONENT(SPECIES_DRAGONITE); @@ -274,8 +274,8 @@ SINGLE_BATTLE_TEST("Embargo disables the effect of the Drive items on the move T { u32 heldItem; - PARAMETRIZE{ heldItem = ITEM_NONE; } - PARAMETRIZE{ heldItem = ITEM_SHOCK_DRIVE; } + PARAMETRIZE { heldItem = ITEM_NONE; } + PARAMETRIZE { heldItem = ITEM_SHOCK_DRIVE; } GIVEN { PLAYER(SPECIES_GENESECT) { Item(heldItem); }; OPPONENT(SPECIES_GYARADOS); @@ -295,8 +295,8 @@ SINGLE_BATTLE_TEST("Embargo disables the effect of the Memory items on the move { u32 heldItem; - PARAMETRIZE{ heldItem = ITEM_NONE; } - PARAMETRIZE{ heldItem = ITEM_FIRE_MEMORY; } + PARAMETRIZE { heldItem = ITEM_NONE; } + PARAMETRIZE { heldItem = ITEM_FIRE_MEMORY; } GIVEN { PLAYER(SPECIES_SILVALLY) { Item(heldItem); }; OPPONENT(SPECIES_VENUSAUR); diff --git a/test/battle/move_effect/flame_burst.c b/test/battle/move_effect/flame_burst.c new file mode 100644 index 000000000..d222325a7 --- /dev/null +++ b/test/battle/move_effect/flame_burst.c @@ -0,0 +1,24 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_FLAME_BURST].additionalEffects->moveEffect == MOVE_EFFECT_FLAME_BURST); +} + +// Flame Burst AoE is supposed to hit through Substitute +DOUBLE_BATTLE_TEST("Flame Burst Substitute") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_SUBSTITUTE].effect == EFFECT_SUBSTITUTE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SUBSTITUTE); MOVE(playerRight, MOVE_FLAME_BURST, target: opponentRight); } + } SCENE { + MESSAGE("The bursting flames hit Foe Wynaut!"); + NOT MESSAGE("The SUBSTITUTE took damage for Foe Wynaut!"); + } +} diff --git a/test/battle/move_effect/focus_punch.c b/test/battle/move_effect/focus_punch.c index 5bfb606b0..355c071a9 100644 --- a/test/battle/move_effect/focus_punch.c +++ b/test/battle/move_effect/focus_punch.c @@ -74,3 +74,28 @@ DOUBLE_BATTLE_TEST("Focus Punch activation is based on Speed") MESSAGE("Foe Wobbuffet lost its focus and couldn't move!"); } } + +AI_SINGLE_BATTLE_TEST("AI won't use Focus Punch if it predicts a damaging move") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_MAGNEZONE) { Moves(MOVE_THUNDER_WAVE, MOVE_FLASH_CANNON, MOVE_DISCHARGE, MOVE_TRI_ATTACK); } + OPPONENT(SPECIES_BRELOOM) { Moves(MOVE_FOCUS_PUNCH, MOVE_SEED_BOMB); } + } WHEN { + TURN { MOVE(player, MOVE_DISCHARGE); EXPECT_MOVE(opponent, MOVE_FOCUS_PUNCH); } + TURN { MOVE(player, MOVE_DISCHARGE); EXPECT_MOVE(opponent, MOVE_SEED_BOMB); } + } +} + +AI_SINGLE_BATTLE_TEST("AI will Incapacitate -> Substitute -> Focus Punch if able") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_MAGNEZONE) { Moves(MOVE_THUNDER_WAVE, MOVE_FLASH_CANNON, MOVE_DISCHARGE, MOVE_TRI_ATTACK); } + OPPONENT(SPECIES_BRELOOM) { Moves(MOVE_SPORE, MOVE_FOCUS_PUNCH, MOVE_SUBSTITUTE, MOVE_SEED_BOMB); } + } WHEN { + TURN { MOVE(player, MOVE_DISCHARGE); EXPECT_MOVE(opponent, MOVE_SPORE); } + TURN { MOVE(player, MOVE_DISCHARGE); EXPECT_MOVE(opponent, MOVE_SUBSTITUTE); } + TURN { MOVE(player, MOVE_DISCHARGE); EXPECT_MOVE(opponent, MOVE_FOCUS_PUNCH); } + } +} diff --git a/test/battle/move_effect/fury_cutter.c b/test/battle/move_effect/fury_cutter.c new file mode 100644 index 000000000..cf3871964 --- /dev/null +++ b/test/battle/move_effect/fury_cutter.c @@ -0,0 +1,38 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_FURY_CUTTER].effect == EFFECT_FURY_CUTTER); +} + +SINGLE_BATTLE_TEST("Fury Cutter power doubles with each use, up to 160 power") +{ + s16 damage[6]; + int turn; + int maxTurns; + + if (B_UPDATED_MOVE_DATA >= GEN_6) + maxTurns = 4; + else if (B_UPDATED_MOVE_DATA == GEN_5) + maxTurns = 5; + else + maxTurns = 6; + + GIVEN { + PLAYER(SPECIES_CROBAT); + OPPONENT(SPECIES_LINOONE) { HP(900); } + } WHEN { + for (turn = 0; turn < maxTurns; turn++) + TURN { MOVE(player, MOVE_FURY_CUTTER); } + } SCENE { + for (turn = 0; turn < maxTurns; turn++) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_CUTTER, player); + HP_BAR(opponent, captureDamage: &damage[turn]); + } + } THEN { + for (turn = 1; turn < maxTurns - 1; turn++) + EXPECT_MUL_EQ(damage[turn - 1], UQ_4_12(2.0), damage[turn]); + EXPECT_EQ(damage[maxTurns - 2], damage[maxTurns - 1]); + } +} diff --git a/test/battle/move_effect/hit_escape.c b/test/battle/move_effect/hit_escape.c index dd1372a01..0c96bd4e7 100644 --- a/test/battle/move_effect/hit_escape.c +++ b/test/battle/move_effect/hit_escape.c @@ -126,6 +126,7 @@ SINGLE_BATTLE_TEST("Held items are consumed immediately after a mon switched in ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("2 sent out Wynaut!"); @@ -137,7 +138,6 @@ SINGLE_BATTLE_TEST("Held items are consumed immediately after a mon switched in SINGLE_BATTLE_TEST("Held items are consumed immediately after a mon switched in by U-turn and Intimidate activates after it: opposing side") { - KNOWN_FAILING; GIVEN { PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } @@ -149,7 +149,7 @@ SINGLE_BATTLE_TEST("Held items are consumed immediately after a mon switched in ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); HP_BAR(opponent); - NOT ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); MESSAGE("2 sent out Wynaut!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); diff --git a/test/battle/move_effect/ion_deluge.c b/test/battle/move_effect/ion_deluge.c index ab64667b0..93e147e07 100644 --- a/test/battle/move_effect/ion_deluge.c +++ b/test/battle/move_effect/ion_deluge.c @@ -41,7 +41,7 @@ WILD_BATTLE_TEST("Ion Deluge works the same way as always when used by a mon wit NONE_OF { ABILITY_POPUP(opponent, ability); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); - MESSAGE("Wild Zebstrika's Sp.Atk rose!"); + MESSAGE("Wild Zebstrika's Sp. Atk rose!"); MESSAGE("Wild Zebstrika's Speed rose!"); } MESSAGE("A deluge of ions showers the battlefield!"); diff --git a/test/battle/move_effect/photon_geyser.c b/test/battle/move_effect/photon_geyser.c index 3f46547ee..986d3865a 100644 --- a/test/battle/move_effect/photon_geyser.c +++ b/test/battle/move_effect/photon_geyser.c @@ -23,7 +23,7 @@ SINGLE_BATTLE_TEST("Photon Geyser can be mirror coated if it is a special move") } } -SINGLE_BATTLE_TEST("Photon Geyser can be countered if it is a physcal move") +SINGLE_BATTLE_TEST("Photon Geyser can be countered if it is a physical move") { GIVEN { PLAYER(SPECIES_WOBBUFFET) { Attack(110); SpAttack(100); } diff --git a/test/battle/move_effect/plasma_fists.c b/test/battle/move_effect/plasma_fists.c index ec9dd3718..bdd3ed4f4 100644 --- a/test/battle/move_effect/plasma_fists.c +++ b/test/battle/move_effect/plasma_fists.c @@ -47,20 +47,19 @@ SINGLE_BATTLE_TEST("Plasma Fists turns normal moves into electric for the remain } } -SINGLE_BATTLE_TEST("Plasma Fists type-changing effect is applied after Pixilate") +SINGLE_BATTLE_TEST("Plasma Fists type-changing effect does not override Pixilate") { GIVEN { PLAYER(SPECIES_KRABBY) { Speed(300); }; - OPPONENT(SPECIES_ALTARIA) { Speed(1); Item(ITEM_ALTARIANITE); } + OPPONENT(SPECIES_SYLVEON) { Speed(1); Ability(ABILITY_PIXILATE); } } WHEN { - TURN { MOVE(player, MOVE_PLASMA_FISTS); MOVE(opponent, MOVE_EMBER, gimmick: GIMMICK_MEGA); } + TURN { MOVE(player, MOVE_PLASMA_FISTS); MOVE(opponent, MOVE_TACKLE); } } SCENE { - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); MESSAGE("Krabby used Plasma Fists!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_PLASMA_FISTS, player); MESSAGE("A deluge of ions showers the battlefield!"); - MESSAGE("Foe Altaria used Ember!"); - ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + MESSAGE("Foe Sylveon used Tackle!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); NOT MESSAGE("It's super effective!"); } } diff --git a/test/battle/move_effect/roost.c b/test/battle/move_effect/roost.c index 1ae84dd08..b4c0186bf 100644 --- a/test/battle/move_effect/roost.c +++ b/test/battle/move_effect/roost.c @@ -111,24 +111,24 @@ SINGLE_BATTLE_TEST("Roost suppresses the user's Flying-typing this turn, then re SINGLE_BATTLE_TEST("Roost, if used by a Flying/Flying type, treats the user as a Normal-type (or Typeless in Gen. 4) until the end of the turn") { u32 damagingMove; - PARAMETRIZE{ damagingMove = MOVE_POUND; } - PARAMETRIZE{ damagingMove = MOVE_KARATE_CHOP; } - PARAMETRIZE{ damagingMove = MOVE_GUST; } - PARAMETRIZE{ damagingMove = MOVE_POISON_STING; } - PARAMETRIZE{ damagingMove = MOVE_EARTHQUAKE; } - PARAMETRIZE{ damagingMove = MOVE_ROCK_THROW; } - PARAMETRIZE{ damagingMove = MOVE_LEECH_LIFE; } - PARAMETRIZE{ damagingMove = MOVE_LICK; } - PARAMETRIZE{ damagingMove = MOVE_STEEL_WING; } - PARAMETRIZE{ damagingMove = MOVE_EMBER; } - PARAMETRIZE{ damagingMove = MOVE_WATER_GUN; } - PARAMETRIZE{ damagingMove = MOVE_VINE_WHIP; } - PARAMETRIZE{ damagingMove = MOVE_THUNDER_SHOCK; } - PARAMETRIZE{ damagingMove = MOVE_CONFUSION; } - PARAMETRIZE{ damagingMove = MOVE_ICE_BEAM; } - PARAMETRIZE{ damagingMove = MOVE_DRAGON_BREATH; } - PARAMETRIZE{ damagingMove = MOVE_BITE; } - PARAMETRIZE{ damagingMove = MOVE_DISARMING_VOICE; } + PARAMETRIZE { damagingMove = MOVE_POUND; } + PARAMETRIZE { damagingMove = MOVE_KARATE_CHOP; } + PARAMETRIZE { damagingMove = MOVE_GUST; } + PARAMETRIZE { damagingMove = MOVE_POISON_STING; } + PARAMETRIZE { damagingMove = MOVE_EARTHQUAKE; } + PARAMETRIZE { damagingMove = MOVE_ROCK_THROW; } + PARAMETRIZE { damagingMove = MOVE_LEECH_LIFE; } + PARAMETRIZE { damagingMove = MOVE_LICK; } + PARAMETRIZE { damagingMove = MOVE_STEEL_WING; } + PARAMETRIZE { damagingMove = MOVE_EMBER; } + PARAMETRIZE { damagingMove = MOVE_WATER_GUN; } + PARAMETRIZE { damagingMove = MOVE_VINE_WHIP; } + PARAMETRIZE { damagingMove = MOVE_THUNDER_SHOCK; } + PARAMETRIZE { damagingMove = MOVE_CONFUSION; } + PARAMETRIZE { damagingMove = MOVE_ICE_BEAM; } + PARAMETRIZE { damagingMove = MOVE_DRAGON_BREATH; } + PARAMETRIZE { damagingMove = MOVE_BITE; } + PARAMETRIZE { damagingMove = MOVE_DISARMING_VOICE; } GIVEN { ASSUME(gSpeciesInfo[SPECIES_TORNADUS].types[0] == TYPE_FLYING); @@ -179,24 +179,24 @@ SINGLE_BATTLE_TEST("Roost, if used by a Flying/Flying type, treats the user as a SINGLE_BATTLE_TEST("Roost, if used by a Mystery/Flying type, treats the user as a Mystery/Mystery type until the end of the turn") { u32 damagingMove; - PARAMETRIZE{ damagingMove = MOVE_POUND; } - PARAMETRIZE{ damagingMove = MOVE_KARATE_CHOP; } - PARAMETRIZE{ damagingMove = MOVE_GUST; } - PARAMETRIZE{ damagingMove = MOVE_POISON_STING; } - PARAMETRIZE{ damagingMove = MOVE_EARTHQUAKE; } - PARAMETRIZE{ damagingMove = MOVE_ROCK_THROW; } - PARAMETRIZE{ damagingMove = MOVE_LEECH_LIFE; } - PARAMETRIZE{ damagingMove = MOVE_LICK; } - PARAMETRIZE{ damagingMove = MOVE_STEEL_WING; } - PARAMETRIZE{ damagingMove = MOVE_EMBER; } - PARAMETRIZE{ damagingMove = MOVE_WATER_GUN; } - PARAMETRIZE{ damagingMove = MOVE_VINE_WHIP; } - PARAMETRIZE{ damagingMove = MOVE_THUNDER_SHOCK; } - PARAMETRIZE{ damagingMove = MOVE_CONFUSION; } - PARAMETRIZE{ damagingMove = MOVE_ICE_BEAM; } - PARAMETRIZE{ damagingMove = MOVE_DRAGON_BREATH; } - PARAMETRIZE{ damagingMove = MOVE_BITE; } - PARAMETRIZE{ damagingMove = MOVE_DISARMING_VOICE; } + PARAMETRIZE { damagingMove = MOVE_POUND; } + PARAMETRIZE { damagingMove = MOVE_KARATE_CHOP; } + PARAMETRIZE { damagingMove = MOVE_GUST; } + PARAMETRIZE { damagingMove = MOVE_POISON_STING; } + PARAMETRIZE { damagingMove = MOVE_EARTHQUAKE; } + PARAMETRIZE { damagingMove = MOVE_ROCK_THROW; } + PARAMETRIZE { damagingMove = MOVE_LEECH_LIFE; } + PARAMETRIZE { damagingMove = MOVE_LICK; } + PARAMETRIZE { damagingMove = MOVE_STEEL_WING; } + PARAMETRIZE { damagingMove = MOVE_EMBER; } + PARAMETRIZE { damagingMove = MOVE_WATER_GUN; } + PARAMETRIZE { damagingMove = MOVE_VINE_WHIP; } + PARAMETRIZE { damagingMove = MOVE_THUNDER_SHOCK; } + PARAMETRIZE { damagingMove = MOVE_CONFUSION; } + PARAMETRIZE { damagingMove = MOVE_ICE_BEAM; } + PARAMETRIZE { damagingMove = MOVE_DRAGON_BREATH; } + PARAMETRIZE { damagingMove = MOVE_BITE; } + PARAMETRIZE { damagingMove = MOVE_DISARMING_VOICE; } GIVEN { ASSUME(gSpeciesInfo[SPECIES_MOLTRES].types[0] == TYPE_FIRE); diff --git a/test/battle/move_effect/shell_side_arm.c b/test/battle/move_effect/shell_side_arm.c index f0b3dd74d..13458e39a 100644 --- a/test/battle/move_effect/shell_side_arm.c +++ b/test/battle/move_effect/shell_side_arm.c @@ -31,7 +31,7 @@ SINGLE_BATTLE_TEST("Shell Side Arm can be mirror coated if it is special") } } -SINGLE_BATTLE_TEST("Shell Side Arm does not change catogory mid-turn") +SINGLE_BATTLE_TEST("Shell Side Arm does not change category mid-turn") { s16 damage[3]; @@ -56,7 +56,7 @@ SINGLE_BATTLE_TEST("Shell Side Arm does not change catogory mid-turn") } } -DOUBLE_BATTLE_TEST("Shell Side Arm is choosing it's type for each battler on the field") +DOUBLE_BATTLE_TEST("Shell Side Arm chooses its category for each battler on the field") { GIVEN { PLAYER(SPECIES_WOBBUFFET) { Speed(10); Moves(MOVE_SHELL_SIDE_ARM); } diff --git a/test/battle/move_effect/spicy_extract.c b/test/battle/move_effect/spicy_extract.c index 3642bf936..8a66003ec 100644 --- a/test/battle/move_effect/spicy_extract.c +++ b/test/battle/move_effect/spicy_extract.c @@ -170,7 +170,6 @@ AI_DOUBLE_BATTLE_TEST("Spicy Extract user will use it if partner holds Clear Amu PARAMETRIZE { move = MOVE_TACKLE; } PARAMETRIZE { move = MOVE_SWIFT;} - AI_LOG; GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET) { Speed(10); } diff --git a/test/battle/move_effect/sticky_web.c b/test/battle/move_effect/sticky_web.c index f0017c3ca..035568dd7 100644 --- a/test/battle/move_effect/sticky_web.c +++ b/test/battle/move_effect/sticky_web.c @@ -56,7 +56,7 @@ DOUBLE_BATTLE_TEST("Sticky Web lowers Speed by 1 in a double battle after Explos OPPONENT(SPECIES_WOBBUFFET) {HP(1); Speed(1);} OPPONENT(SPECIES_WOBBUFFET) {HP(1); Speed(1);} OPPONENT(SPECIES_WYNAUT) {Speed(10);} - OPPONENT(SPECIES_WYNAUT) {Speed(10);} + OPPONENT(SPECIES_ALAKAZAM) {Speed(100);} } WHEN { TURN { MOVE(playerRight, MOVE_STICKY_WEB); MOVE(playerLeft, MOVE_EXPLOSION); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 2); SEND_OUT(opponentRight, 3); } TURN {} @@ -65,13 +65,13 @@ DOUBLE_BATTLE_TEST("Sticky Web lowers Speed by 1 in a double battle after Explos MESSAGE("A sticky web spreads out on the ground around the opposing team!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); MESSAGE("2 sent out Wynaut!"); + MESSAGE("2 sent out Alakazam!"); + MESSAGE("Foe Alakazam was caught in a Sticky Web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Foe Alakazam's Speed fell!"); MESSAGE("Foe Wynaut was caught in a Sticky Web!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); MESSAGE("Foe Wynaut's Speed fell!"); - MESSAGE("2 sent out Wynaut!"); - MESSAGE("Foe Wynaut was caught in a Sticky Web!"); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); - MESSAGE("Foe Wynaut's Speed fell!"); } } diff --git a/test/battle/move_effect/strength_sap.c b/test/battle/move_effect/strength_sap.c index ef8ef3259..474707427 100644 --- a/test/battle/move_effect/strength_sap.c +++ b/test/battle/move_effect/strength_sap.c @@ -10,8 +10,8 @@ SINGLE_BATTLE_TEST("Strength Sap lowers Attack by 1 and restores HP based on tar { u32 atkStat = 0; - PARAMETRIZE{ atkStat = 100; } - PARAMETRIZE{ atkStat = 50; } + PARAMETRIZE { atkStat = 100; } + PARAMETRIZE { atkStat = 50; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { HP(200); } @@ -35,8 +35,8 @@ SINGLE_BATTLE_TEST("Strength Sap works exactly the same when attacker is behind { u32 atkStat = 0; - PARAMETRIZE{ atkStat = 100; } - PARAMETRIZE{ atkStat = 50; } + PARAMETRIZE { atkStat = 100; } + PARAMETRIZE { atkStat = 50; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { HP(200); } @@ -65,7 +65,7 @@ SINGLE_BATTLE_TEST("Strength Sap lowers Attack by 1 and restores HP based on tar for (j = 0; j <= MAX_STAT_STAGE; j++) { if (j == DEFAULT_STAT_STAGE - 1) { continue; } // Ignore -6, because Strength Sap won't work otherwise - PARAMETRIZE{ statStage = j; } + PARAMETRIZE { statStage = j; } } GIVEN { diff --git a/test/battle/move_effect/tera_starstorm.c b/test/battle/move_effect/tera_starstorm.c index 44e42f474..8b1ff41c7 100644 --- a/test/battle/move_effect/tera_starstorm.c +++ b/test/battle/move_effect/tera_starstorm.c @@ -59,3 +59,18 @@ SINGLE_BATTLE_TEST("Tera Starstorm becomes a physical move if the user is Terapa EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.5), results[1].damage); } } + +SINGLE_BATTLE_TEST("Tera Starstorm remains Normal-type if used by Pokemon other than Terapagos") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TERA_STARSTORM].type == TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_MISDREAVUS].types[0] == TYPE_GHOST); + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_MISDREAVUS); + } WHEN { + TURN { MOVE(player, MOVE_TERA_STARSTORM, gimmick: GIMMICK_TERA); } + } SCENE { + MESSAGE("Wobbuffet used Tera Starstorm!"); + MESSAGE("It doesn't affect Foe Misdreavus…"); + } +} diff --git a/test/battle/move_effect/tidy_up.c b/test/battle/move_effect/tidy_up.c index 463fb65af..338ba90dc 100644 --- a/test/battle/move_effect/tidy_up.c +++ b/test/battle/move_effect/tidy_up.c @@ -74,13 +74,14 @@ SINGLE_BATTLE_TEST("Tidy Up removes Substitute") } } -AI_SINGLE_BATTLE_TEST("AI prefers to keep it's substitute over removing hazards if target is slower") +AI_SINGLE_BATTLE_TEST("AI prefers to keep its substitute over removing hazards if target is slower") { GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET) { Speed(50); Status1(STATUS1_PARALYSIS); Moves(MOVE_SLEEP_POWDER, MOVE_STEALTH_ROCK, MOVE_CELEBRATE); } OPPONENT(SPECIES_WOBBUFFET) { Speed(100); Moves(MOVE_BITE, MOVE_TACKLE, MOVE_SUBSTITUTE, MOVE_TIDY_UP); } } WHEN { + TURN { MOVE(player, MOVE_STEALTH_ROCK); EXPECT_MOVE(opponent, MOVE_TIDY_UP); } TURN { MOVE(player, MOVE_STEALTH_ROCK); EXPECT_MOVE(opponent, MOVE_SUBSTITUTE); } TURN { EXPECT_MOVE(opponent, MOVE_BITE); } } @@ -93,6 +94,7 @@ AI_SINGLE_BATTLE_TEST("AI will try to remove hazards if slower then target even PLAYER(SPECIES_WOBBUFFET) { Speed(100); Status1(STATUS1_BURN); Moves(MOVE_SLEEP_POWDER, MOVE_STEALTH_ROCK, MOVE_CELEBRATE); } OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Moves(MOVE_BITE, MOVE_TACKLE, MOVE_SUBSTITUTE, MOVE_TIDY_UP); } } WHEN { + TURN { MOVE(player, MOVE_STEALTH_ROCK); EXPECT_MOVE(opponent, MOVE_TIDY_UP); } TURN { MOVE(player, MOVE_STEALTH_ROCK); EXPECT_MOVE(opponent, MOVE_SUBSTITUTE); } TURN { EXPECT_MOVE(opponent, MOVE_TIDY_UP); } } diff --git a/test/battle/move_effect/toxic.c b/test/battle/move_effect/toxic.c index 35bfaa064..804ed56b8 100644 --- a/test/battle/move_effect/toxic.c +++ b/test/battle/move_effect/toxic.c @@ -48,3 +48,21 @@ SINGLE_BATTLE_TEST("Toxic cannot miss if used by a Poison-type") } } } + +AI_SINGLE_BATTLE_TEST("AI avoids toxic when it can not poison target") +{ + u32 species, ability; + + PARAMETRIZE { species = SPECIES_SNORLAX; ability = ABILITY_IMMUNITY; } + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { species = SPECIES_NACLI; ability = ABILITY_PURIFYING_SALT; } + PARAMETRIZE { species = SPECIES_BULBASAUR; ability = ABILITY_OVERGROW; } + + GIVEN { + 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_CELEBRATE, MOVE_TOXIC); } + } WHEN { + TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_TOXIC); } // Both get -10 + } +} diff --git a/test/battle/move_effect/two_turns_attack.c b/test/battle/move_effect/two_turns_attack.c index 9225afb48..4b7adc6f8 100644 --- a/test/battle/move_effect/two_turns_attack.c +++ b/test/battle/move_effect/two_turns_attack.c @@ -243,8 +243,8 @@ SINGLE_BATTLE_TEST("Solar Beam and Solar Blade can be used instantly in Sunlight SINGLE_BATTLE_TEST("Solar Beam's power is halved in Rain", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_RAIN_DANCE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_RAIN_DANCE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -261,8 +261,8 @@ SINGLE_BATTLE_TEST("Solar Beam's power is halved in Rain", s16 damage) SINGLE_BATTLE_TEST("Solar Blade's power is halved in Rain", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_RAIN_DANCE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_RAIN_DANCE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); @@ -279,8 +279,8 @@ SINGLE_BATTLE_TEST("Solar Blade's power is halved in Rain", s16 damage) SINGLE_BATTLE_TEST("Solar Beam's power is halved in a Sandstorm", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_SANDSTORM; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SANDSTORM; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SAFETY_GOGGLES); }; @@ -297,8 +297,8 @@ SINGLE_BATTLE_TEST("Solar Beam's power is halved in a Sandstorm", s16 damage) SINGLE_BATTLE_TEST("Solar Blade's power is halved in a Sandstorm", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_SANDSTORM; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SANDSTORM; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SAFETY_GOGGLES); }; @@ -315,8 +315,8 @@ SINGLE_BATTLE_TEST("Solar Blade's power is halved in a Sandstorm", s16 damage) SINGLE_BATTLE_TEST("Solar Beam's power is halved in Hail", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_HAIL; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SAFETY_GOGGLES); }; @@ -333,8 +333,8 @@ SINGLE_BATTLE_TEST("Solar Beam's power is halved in Hail", s16 damage) SINGLE_BATTLE_TEST("Solar Blade's power is halved in Hail", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_HAIL; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SAFETY_GOGGLES); }; @@ -351,8 +351,8 @@ SINGLE_BATTLE_TEST("Solar Blade's power is halved in Hail", s16 damage) SINGLE_BATTLE_TEST("Solar Beam's power is halved in Snow", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -369,8 +369,8 @@ SINGLE_BATTLE_TEST("Solar Beam's power is halved in Snow", s16 damage) SINGLE_BATTLE_TEST("Solar Blade's power is halved in Snow", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); diff --git a/test/battle/move_effect/upper_hand.c b/test/battle/move_effect/upper_hand.c index 04f2b8522..6a041182f 100644 --- a/test/battle/move_effect/upper_hand.c +++ b/test/battle/move_effect/upper_hand.c @@ -116,3 +116,19 @@ SINGLE_BATTLE_TEST("Upper Hand is boosted by Sheer Force") HP_BAR(player); } } + +AI_SINGLE_BATTLE_TEST("AI won't use Upper Hand unless it has seen a priority move") +{ + u16 move; + PARAMETRIZE { move = MOVE_TACKLE; } + PARAMETRIZE { move = MOVE_QUICK_ATTACK; } + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + ASSUME(gMovesInfo[MOVE_QUICK_ATTACK].priority == 1); + PLAYER(SPECIES_WOBBUFFET) {Moves(move); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_UPPER_HAND, MOVE_KARATE_CHOP); } + } WHEN { + TURN { MOVE(player, move); EXPECT_MOVE(opponent, MOVE_KARATE_CHOP); } + TURN { MOVE(player, move); EXPECT_MOVE(opponent, move == MOVE_QUICK_ATTACK ? MOVE_UPPER_HAND : MOVE_KARATE_CHOP); } + } +} diff --git a/test/battle/move_effect/weather_ball.c b/test/battle/move_effect/weather_ball.c index 38c902f50..1656b60d8 100644 --- a/test/battle/move_effect/weather_ball.c +++ b/test/battle/move_effect/weather_ball.c @@ -9,8 +9,8 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to a Fire-type move in Sunlight", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_SUNNY_DAY; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SUNNY_DAY; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_MEGANIUM); @@ -27,8 +27,8 @@ SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to a Fire-type move SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to a Water-type move in Rain", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_RAIN_DANCE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_RAIN_DANCE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_ARCANINE); @@ -45,8 +45,8 @@ SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to a Water-type mov SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to a Rock-type move in a Sandstorm", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_SANDSTORM; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SANDSTORM; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_MAGMAR) { Item(ITEM_SAFETY_GOGGLES); }; @@ -63,9 +63,9 @@ SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to a Rock-type move SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to an Ice-type move in Hail and Snow", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_HAIL; } - PARAMETRIZE{ move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_DRAGONAIR) { Item(ITEM_SAFETY_GOGGLES); }; diff --git a/test/battle/move_effect_secondary/dire_claw.c b/test/battle/move_effect_secondary/dire_claw.c index 67c361bac..35c6e73d9 100644 --- a/test/battle/move_effect_secondary/dire_claw.c +++ b/test/battle/move_effect_secondary/dire_claw.c @@ -67,7 +67,8 @@ SINGLE_BATTLE_TEST("Dire Claw cannot poison/paralyze/cause to fall asleep pokemo u8 statusAnim; u16 species, ability; u32 rng; - PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_RAICHU; ability = ABILITY_LIGHTNING_ROD; } + if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) + PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_RAICHU; ability = ABILITY_LIGHTNING_ROD; } PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_JOLTEON; ability = ABILITY_VOLT_ABSORB; } PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_ELECTIVIRE; ability = ABILITY_MOTOR_DRIVE; } PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = MOVE_EFFECT_POISON; species = SPECIES_ZANGOOSE; ability = ABILITY_IMMUNITY; } diff --git a/test/battle/move_effect_secondary/freeze.c b/test/battle/move_effect_secondary/freeze.c index d7a182b3d..1539c8a25 100644 --- a/test/battle/move_effect_secondary/freeze.c +++ b/test/battle/move_effect_secondary/freeze.c @@ -7,7 +7,11 @@ ASSUMPTIONS ASSUME(gMovesInfo[MOVE_BLIZZARD].accuracy == 70); } +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Powder Snow inflicts frostbite") +#else SINGLE_BATTLE_TEST("Powder Snow inflicts freeze") +#endif { GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -18,11 +22,15 @@ SINGLE_BATTLE_TEST("Powder Snow inflicts freeze") ANIMATION(ANIM_TYPE_MOVE, MOVE_POWDER_SNOW, player); HP_BAR(opponent); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRZ, opponent); - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } } +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Powder Snow cannot frostbite an Ice-type Pokémon") +#else SINGLE_BATTLE_TEST("Powder Snow cannot freeze an Ice-type Pokémon") +#endif { GIVEN { ASSUME(gSpeciesInfo[SPECIES_SNORUNT].types[0] == TYPE_ICE); @@ -35,7 +43,7 @@ SINGLE_BATTLE_TEST("Powder Snow cannot freeze an Ice-type Pokémon") HP_BAR(opponent); NONE_OF { ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRZ, opponent); - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } } } @@ -68,10 +76,18 @@ SINGLE_BATTLE_TEST("Blizzard bypasses accuracy checks in Hail and Snow") } #if B_STATUS_TYPE_IMMUNITY > GEN_1 +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Freezing Glare should frostbite Psychic-types") +#else SINGLE_BATTLE_TEST("Freezing Glare should freeze Psychic-types") +#endif +#else +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Freezing Glare shouldn't freeze Psychic-types") #else SINGLE_BATTLE_TEST("Freezing Glare shouldn't freeze Psychic-types") #endif +#endif { GIVEN { ASSUME(gSpeciesInfo[SPECIES_ARTICUNO_GALARIAN].types[0] == TYPE_PSYCHIC); @@ -86,11 +102,11 @@ SINGLE_BATTLE_TEST("Freezing Glare shouldn't freeze Psychic-types") HP_BAR(opponent); #if B_STATUS_TYPE_IMMUNITY > GEN_1 ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRZ, opponent); - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); #else NONE_OF { ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRZ, opponent); - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } #endif } diff --git a/test/battle/move_effect_secondary/tri_attack.c b/test/battle/move_effect_secondary/tri_attack.c index 97eaa76ad..e84573dcd 100644 --- a/test/battle/move_effect_secondary/tri_attack.c +++ b/test/battle/move_effect_secondary/tri_attack.c @@ -6,7 +6,11 @@ ASSUMPTIONS ASSUME(MoveHasAdditionalEffect(MOVE_TRI_ATTACK, MOVE_EFFECT_TRI_ATTACK) == TRUE); } +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Tri Attack can inflict paralysis, burn or frostbite") +#else SINGLE_BATTLE_TEST("Tri Attack can inflict paralysis, burn or freeze") +#endif { u8 statusAnim; PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; } @@ -26,14 +30,18 @@ SINGLE_BATTLE_TEST("Tri Attack can inflict paralysis, burn or freeze") if (statusAnim == B_ANIM_STATUS_BRN) { STATUS_ICON(opponent, burn: TRUE); } else if (statusAnim == B_ANIM_STATUS_FRZ) { - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } else if (statusAnim == B_ANIM_STATUS_PRZ) { STATUS_ICON(opponent, paralysis: TRUE); } } } +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/frostbite electric/fire/ice types respectively") +#else SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze electric/fire/ice types respectively") +#endif { u8 statusAnim; u16 species; @@ -42,7 +50,7 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze electric/fire/ice typ PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_RAICHU; } #endif // B_PARALYZE_ELECTRIC PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_ARCANINE; } - PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE; species = SPECIES_GLALIE; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE_OR_FROSTBITE; species = SPECIES_GLALIE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(species); @@ -57,7 +65,7 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze electric/fire/ice typ if (statusAnim == B_ANIM_STATUS_BRN) { STATUS_ICON(opponent, burn: TRUE); } else if (statusAnim == B_ANIM_STATUS_FRZ) { - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } else if (statusAnim == B_ANIM_STATUS_PRZ) { STATUS_ICON(opponent, paralysis: TRUE); } @@ -65,7 +73,11 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze electric/fire/ice typ } } +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/frostbite pokemon with abilities preventing respective statuses") +#else SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze pokemon with abilities preventing respective statuses") +#endif { u8 statusAnim; u16 species, ability; @@ -75,8 +87,8 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze pokemon with abilitie PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_DEWPIDER; ability = ABILITY_WATER_BUBBLE; } PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_SEAKING; ability = ABILITY_WATER_VEIL; } PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } - PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE; species = SPECIES_CAMERUPT; ability = ABILITY_MAGMA_ARMOR; } - PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE; species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE_OR_FROSTBITE; species = SPECIES_CAMERUPT; ability = ABILITY_MAGMA_ARMOR; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE_OR_FROSTBITE; species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -92,7 +104,7 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze pokemon with abilitie if (statusAnim == B_ANIM_STATUS_BRN) { STATUS_ICON(opponent, burn: TRUE); } else if (statusAnim == B_ANIM_STATUS_FRZ) { - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } else if (statusAnim == B_ANIM_STATUS_PRZ) { STATUS_ICON(opponent, paralysis: TRUE); } @@ -100,13 +112,17 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze pokemon with abilitie } } +#if B_USE_FROSTBITE == TRUE +SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/frostbite a mon which is already statused") +#else SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze a mon which is already statused") +#endif { u8 statusAnim; u32 rng; PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; } PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; } - PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE_OR_FROSTBITE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); } @@ -121,7 +137,7 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze a mon which is alread if (statusAnim == B_ANIM_STATUS_BRN) { STATUS_ICON(opponent, burn: TRUE); } else if (statusAnim == B_ANIM_STATUS_FRZ) { - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } else if (statusAnim == B_ANIM_STATUS_PRZ) { STATUS_ICON(opponent, paralysis: TRUE); } diff --git a/test/battle/move_effects_combined/flinch_status.c b/test/battle/move_effects_combined/flinch_status.c index 6ef9e09bf..bad28eac4 100644 --- a/test/battle/move_effects_combined/flinch_status.c +++ b/test/battle/move_effects_combined/flinch_status.c @@ -5,7 +5,7 @@ ASSUMPTIONS { ASSUME(MoveHasAdditionalEffect(MOVE_THUNDER_FANG, MOVE_EFFECT_PARALYSIS) == TRUE); ASSUME(MoveHasAdditionalEffect(MOVE_THUNDER_FANG, MOVE_EFFECT_FLINCH) == TRUE); - ASSUME(MoveHasAdditionalEffect(MOVE_ICE_FANG, MOVE_EFFECT_FREEZE) == TRUE); + ASSUME(MoveHasAdditionalEffect(MOVE_ICE_FANG, MOVE_EFFECT_FREEZE_OR_FROSTBITE) == TRUE); ASSUME(MoveHasAdditionalEffect(MOVE_ICE_FANG, MOVE_EFFECT_FLINCH) == TRUE); ASSUME(MoveHasAdditionalEffect(MOVE_FIRE_FANG, MOVE_EFFECT_BURN) == TRUE); ASSUME(MoveHasAdditionalEffect(MOVE_FIRE_FANG, MOVE_EFFECT_FLINCH) == TRUE); @@ -33,7 +33,7 @@ SINGLE_BATTLE_TEST("Thunder, Ice and Fire Fang inflict status 10% of the time") STATUS_ICON(opponent, paralysis: TRUE); } if (move == MOVE_ICE_FANG) { ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRZ, opponent); - STATUS_ICON(opponent, freeze: TRUE); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); } if (move == MOVE_FIRE_FANG) { ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponent); STATUS_ICON(opponent, burn: TRUE); diff --git a/test/battle/move_effects_combined/hurricane.c b/test/battle/move_effects_combined/hurricane.c index eab843e72..61acac664 100644 --- a/test/battle/move_effects_combined/hurricane.c +++ b/test/battle/move_effects_combined/hurricane.c @@ -32,4 +32,42 @@ SINGLE_BATTLE_TEST("Hurricane bypasses accuracy checks in Rain") NONE_OF { MESSAGE("Wobbuffet's attack missed!"); } } } -TO_DO_BATTLE_TEST("Hurricane Veil can hit airborne targets") // Fly, Bounce, Sky Drop + +SINGLE_BATTLE_TEST("Hurricane can hit airborne targets (Fly, Bounce)") +{ + u16 move; + PARAMETRIZE { move = MOVE_FLY; } + PARAMETRIZE { move = MOVE_BOUNCE; } + GIVEN { + ASSUME(gMovesInfo[MOVE_FLY].effect == EFFECT_SEMI_INVULNERABLE); + ASSUME(UNCOMPRESS_BITS(HIHALF(gMovesInfo[MOVE_FLY].argument)) == STATUS3_ON_AIR); + ASSUME(gMovesInfo[MOVE_BOUNCE].effect == EFFECT_SEMI_INVULNERABLE); + ASSUME(UNCOMPRESS_BITS(HIHALF(gMovesInfo[MOVE_BOUNCE].argument)) == STATUS3_ON_AIR); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(move); } + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_HURRICANE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HURRICANE, player); + NONE_OF { MESSAGE("Wobbuffet's attack missed!"); } + } +} + +DOUBLE_BATTLE_TEST("Hurricane can hit airborne targets (Sky Drop)") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_SKY_DROP].effect == EFFECT_SKY_DROP); + ASSUME(UNCOMPRESS_BITS(HIHALF(gMovesInfo[MOVE_SKY_DROP].argument)) == STATUS3_ON_AIR); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SKY_DROP, target: opponentLeft); MOVE(playerRight, MOVE_HURRICANE, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HURRICANE, playerRight); + NONE_OF { MESSAGE("Wobbuffet's attack missed!"); } + } +} diff --git a/test/battle/move_flags/ignores_target_ability.c b/test/battle/move_flags/ignores_target_ability.c new file mode 100644 index 000000000..2836f4838 --- /dev/null +++ b/test/battle/move_flags/ignores_target_ability.c @@ -0,0 +1,74 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_SUNSTEEL_STRIKE].ignoresTargetAbility); + ASSUME(gMovesInfo[MOVE_MOONGEIST_BEAM].ignoresTargetAbility); + ASSUME(gMovesInfo[MOVE_PHOTON_GEYSER].ignoresTargetAbility); +} + +SINGLE_BATTLE_TEST("ignoresTargetAbility moves do not ignore the attacker's own ability", s16 damage) +{ + u32 ability, move; + + PARAMETRIZE { move = MOVE_SUNSTEEL_STRIKE; ability = ABILITY_MAGIC_GUARD; } + PARAMETRIZE { move = MOVE_SUNSTEEL_STRIKE; ability = ABILITY_UNAWARE; } + PARAMETRIZE { move = MOVE_MOONGEIST_BEAM; ability = ABILITY_MAGIC_GUARD; } + PARAMETRIZE { move = MOVE_MOONGEIST_BEAM; ability = ABILITY_UNAWARE; } + PARAMETRIZE { move = MOVE_PHOTON_GEYSER; ability = ABILITY_MAGIC_GUARD; } + PARAMETRIZE { move = MOVE_PHOTON_GEYSER; ability = ABILITY_UNAWARE; } + + ASSUME(gAbilitiesInfo[ABILITY_UNAWARE].breakable); + ASSUME(gMovesInfo[MOVE_IRON_DEFENSE].effect == EFFECT_DEFENSE_UP_2); + ASSUME(gMovesInfo[MOVE_AMNESIA].effect == EFFECT_SPECIAL_DEFENSE_UP_2); + + GIVEN { + PLAYER(SPECIES_CLEFABLE) { Speed(1); Ability(ability); } + OPPONENT(SPECIES_ARON) { Speed(2); } + } WHEN { + if (gMovesInfo[move].category == DAMAGE_CATEGORY_PHYSICAL) + TURN { MOVE(opponent, MOVE_IRON_DEFENSE); MOVE(player, move); } + else + TURN { MOVE(opponent, MOVE_AMNESIA); MOVE(player, move); } + } SCENE { + if (gMovesInfo[move].category == DAMAGE_CATEGORY_PHYSICAL) + ANIMATION(ANIM_TYPE_MOVE, MOVE_IRON_DEFENSE, opponent); + else + ANIMATION(ANIM_TYPE_MOVE, MOVE_AMNESIA, opponent); + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, UQ_4_12(2.0), results[3].damage); + EXPECT_MUL_EQ(results[4].damage, UQ_4_12(2.0), results[5].damage); + } +} + +SINGLE_BATTLE_TEST("ignoresTargetAbility moves do ignore target's abilities", s16 damage) +{ + u32 ability, move; + + PARAMETRIZE { move = MOVE_SUNSTEEL_STRIKE; ability = ABILITY_INNER_FOCUS; } + PARAMETRIZE { move = MOVE_SUNSTEEL_STRIKE; ability = ABILITY_MULTISCALE; } + PARAMETRIZE { move = MOVE_MOONGEIST_BEAM; ability = ABILITY_INNER_FOCUS; } + PARAMETRIZE { move = MOVE_MOONGEIST_BEAM; ability = ABILITY_MULTISCALE; } + PARAMETRIZE { move = MOVE_PHOTON_GEYSER; ability = ABILITY_INNER_FOCUS; } + PARAMETRIZE { move = MOVE_PHOTON_GEYSER; ability = ABILITY_MULTISCALE; } + + ASSUME(gAbilitiesInfo[ABILITY_MULTISCALE].breakable); + + GIVEN { + PLAYER(SPECIES_AZUMARILL); + OPPONENT(SPECIES_DRAGONITE) { Ability(ability); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_EQ(results[2].damage, results[3].damage); + EXPECT_EQ(results[4].damage, results[5].damage); + } +} diff --git a/test/battle/status1/burn.c b/test/battle/status1/burn.c index c49bd7a3f..4da40589f 100644 --- a/test/battle/status1/burn.c +++ b/test/battle/status1/burn.c @@ -35,3 +35,23 @@ SINGLE_BATTLE_TEST("Burn reduces Attack by 50%", s16 damage) EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); } } + +AI_SINGLE_BATTLE_TEST("AI avoids Will-o-Wisp when it can not burn target") +{ + u32 species, ability; + + PARAMETRIZE { species = SPECIES_BUIZEL; ability = ABILITY_WATER_VEIL; } + PARAMETRIZE { species = SPECIES_DEWPIDER; ability = ABILITY_WATER_BUBBLE; } + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { species = SPECIES_ARCTIBAX; ability = ABILITY_THERMAL_EXCHANGE; } + PARAMETRIZE { species = SPECIES_NACLI; ability = ABILITY_PURIFYING_SALT; } + PARAMETRIZE { species = SPECIES_CHARMANDER; ability = ABILITY_BLAZE; } + + GIVEN { + 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_CELEBRATE, MOVE_WILL_O_WISP); } + } WHEN { + TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_WILL_O_WISP); } // Both get -10 + } +} diff --git a/test/battle/status1/paralysis.c b/test/battle/status1/paralysis.c index b6edaf263..558ce4fc3 100644 --- a/test/battle/status1/paralysis.c +++ b/test/battle/status1/paralysis.c @@ -42,3 +42,21 @@ SINGLE_BATTLE_TEST("Paralysis has a 25% chance of skipping the turn") MESSAGE("Wobbuffet is paralyzed! It can't move!"); } } + +AI_SINGLE_BATTLE_TEST("AI avoids Thunder Wave when it can not paralyse target") +{ + u32 species, ability; + + PARAMETRIZE { species = SPECIES_HITMONLEE; ability = ABILITY_LIMBER; } + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { species = SPECIES_NACLI; ability = ABILITY_PURIFYING_SALT; } + PARAMETRIZE { species = SPECIES_PIKACHU; ability = ABILITY_STATIC; } + + GIVEN { + 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_CELEBRATE, MOVE_THUNDER_WAVE); } + } WHEN { + TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_THUNDER_WAVE); } // Both get -10 + } +} diff --git a/test/battle/status1/sleep.c b/test/battle/status1/sleep.c index b3dd403eb..f42f4bd71 100644 --- a/test/battle/status1/sleep.c +++ b/test/battle/status1/sleep.c @@ -21,3 +21,21 @@ SINGLE_BATTLE_TEST("Sleep prevents the battler from using a move") MESSAGE("Wobbuffet used Celebrate!"); } } + +AI_SINGLE_BATTLE_TEST("AI avoids hypnosis when it can not put target to sleep") +{ + u32 species, ability; + + PARAMETRIZE { species = SPECIES_HOOTHOOT; ability = ABILITY_INSOMNIA; } + PARAMETRIZE { species = SPECIES_MANKEY; ability = ABILITY_VITAL_SPIRIT; } + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { species = SPECIES_NACLI; ability = ABILITY_PURIFYING_SALT; } + + GIVEN { + 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_CELEBRATE, MOVE_HYPNOSIS); } + } WHEN { + TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_HYPNOSIS); } // Both get -10 + } +} diff --git a/test/battle/status2/confusion.c b/test/battle/status2/confusion.c new file mode 100644 index 000000000..3c86e5d55 --- /dev/null +++ b/test/battle/status2/confusion.c @@ -0,0 +1,28 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Confusion adds a 50/33% chance to hit self with 40 power") +{ + s16 damage[2]; + + ASSUME(gMovesInfo[MOVE_TACKLE].power == 40); + + PASSES_RANDOMLY(B_CONFUSION_SELF_DMG_CHANCE >= GEN_7 ? 33 : 50, 100, RNG_CONFUSION); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(1); }; + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); }; + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE, WITH_RNG(RNG_DAMAGE_MODIFIER, 0)); MOVE(player, MOVE_CONFUSE_RAY); } + TURN; + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + HP_BAR(player, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); + MESSAGE("Foe Wobbuffet became confused!"); + MESSAGE("Foe Wobbuffet is confused!"); + MESSAGE("It hurt itself in its confusion!"); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} diff --git a/test/battle/terrain/grassy.c b/test/battle/terrain/grassy.c index 678352dc5..862c9052f 100644 --- a/test/battle/terrain/grassy.c +++ b/test/battle/terrain/grassy.c @@ -85,3 +85,27 @@ SINGLE_BATTLE_TEST("Grassy Terrain lasts for 5 turns") MESSAGE("The grass disappeared from the battlefield."); } } + +SINGLE_BATTLE_TEST("Grassy Terrain heals the pokemon on the field for the duration of the terrain, including last turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); }; + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_GRASSY_TERRAIN); } + TURN {} + TURN {} + TURN {} + TURN {} + } SCENE { + MESSAGE("Foe Wobbuffet used Celebrate!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASSY_TERRAIN, player); + MESSAGE("Grass grew to cover the battlefield!"); + MESSAGE("Foe Wobbuffet is healed by the grassy terrain!"); + MESSAGE("Foe Wobbuffet is healed by the grassy terrain!"); + MESSAGE("Foe Wobbuffet is healed by the grassy terrain!"); + MESSAGE("Foe Wobbuffet is healed by the grassy terrain!"); + MESSAGE("Foe Wobbuffet is healed by the grassy terrain!"); + MESSAGE("The grass disappeared from the battlefield."); + } +} diff --git a/test/battle/test_runner_features.c b/test/battle/test_runner_features.c new file mode 100644 index 000000000..6b84c4fc2 --- /dev/null +++ b/test/battle/test_runner_features.c @@ -0,0 +1,27 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS { + int i; + for (i = 0; i < NUM_ABILITY_SLOTS; i++) { + ASSUME(gSpeciesInfo[SPECIES_KADABRA].abilities[i] != ABILITY_QUARK_DRIVE); + ASSUME(gSpeciesInfo[SPECIES_ALAKAZAM].abilities[i] != ABILITY_ELECTRIC_SURGE); + } +} + +SINGLE_BATTLE_TEST("Forced abilities activate on switch-in") +{ + GIVEN { + PLAYER(SPECIES_ALAKAZAM); + PLAYER(SPECIES_KADABRA) { Ability(ABILITY_QUARK_DRIVE); SpAttack(400);} + OPPONENT(SPECIES_ARON); + OPPONENT(SPECIES_ALAKAZAM) { Ability(ABILITY_ELECTRIC_SURGE); }; + } WHEN { + TURN { SWITCH(player, 1); SWITCH(opponent, 1);} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("The Electric Terrain activated Kadabra's Quark Drive!"); + MESSAGE("Kadabra's Sp. Atk was heightened!"); + } +} diff --git a/test/battle/trainer_control.c b/test/battle/trainer_control.c index 231de3ad8..e31d281ef 100644 --- a/test/battle/trainer_control.c +++ b/test/battle/trainer_control.c @@ -113,3 +113,50 @@ TEST("ModifyPersonalityForNature can set any nature") ModifyPersonalityForNature(&personality, nature); EXPECT_EQ(GetNatureFromPersonality(personality), nature); } + +static const struct TrainerMon sTestParty2[] = +{ + { + .species = SPECIES_WYNAUT, + .lvl = 5, + }, + { + .species = SPECIES_WYNAUT, + .lvl = 5, + }, + { + .species = SPECIES_WYNAUT, + .lvl = 5, + }, + { + .species = SPECIES_WYNAUT, + .lvl = 5, + }, + { + .species = SPECIES_WYNAUT, + .lvl = 5, + }, + { + .species = SPECIES_WYNAUT, + .lvl = 5, + }, +}; + +static const struct Trainer sTestTrainer2 = +{ + .trainerName = _("Test2"), + .trainerClass = TRAINER_CLASS_BLACK_BELT, + .party = TRAINER_PARTY(sTestParty2), +}; + +TEST("Trainer Class Balls apply to the entire party") +{ + u32 j; + struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon)); + CreateNPCTrainerPartyFromTrainer(testParty, &sTestTrainer2, TRUE, BATTLE_TYPE_TRAINER); + for(j = 0; j < 6; j++) + { + EXPECT(GetMonData(&testParty[j], MON_DATA_POKEBALL, 0) == gTrainerClasses[sTestTrainer2.trainerClass].ball); + } + Free(testParty); +} diff --git a/test/battle/trainer_control.h b/test/battle/trainer_control.h index 4cc219a4f..994e7d25d 100644 --- a/test/battle/trainer_control.h +++ b/test/battle/trainer_control.h @@ -52,7 +52,7 @@ .isShiny = TRUE, #line 18 .dynamaxLevel = 5, - .useGimmick = GIMMICK_DYNAMAX, + .shouldUseDynamax = TRUE, .moves = { #line 19 MOVE_AIR_SLASH, diff --git a/test/battle/weather/hail.c b/test/battle/weather/hail.c index 567037cca..5caeb8489 100644 --- a/test/battle/weather/hail.c +++ b/test/battle/weather/hail.c @@ -53,3 +53,20 @@ SINGLE_BATTLE_TEST("Hail fails if Desolate Land or Primordial Sea are active") } } } + +DOUBLE_BATTLE_TEST("Hail deals damage based on turn order") +{ + GIVEN { + PLAYER(SPECIES_GLALIE); + PLAYER(SPECIES_WYNAUT) { Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + OPPONENT(SPECIES_WYNAUT) { Speed(3); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_HAIL); } + } SCENE { + NOT HP_BAR(playerLeft); + HP_BAR(opponentRight); + HP_BAR(opponentLeft); + HP_BAR(playerRight); + } +} diff --git a/test/battle/weather/sandstorm.c b/test/battle/weather/sandstorm.c index 5d7a6f171..63b24afd7 100644 --- a/test/battle/weather/sandstorm.c +++ b/test/battle/weather/sandstorm.c @@ -20,8 +20,8 @@ SINGLE_BATTLE_TEST("Sandstorm deals 1/16 damage per turn") SINGLE_BATTLE_TEST("Sandstorm multiplies the special defense of Rock-types by 1.5x", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_SANDSTORM; } - PARAMETRIZE{ move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SANDSTORM; } + PARAMETRIZE { move = MOVE_CELEBRATE; } GIVEN { ASSUME(gMovesInfo[MOVE_SWIFT].category == DAMAGE_CATEGORY_SPECIAL); PLAYER(SPECIES_WOBBUFFET) ; @@ -65,3 +65,20 @@ SINGLE_BATTLE_TEST("Sandstorm damage does not hurt Ground, Rock, and Steel-type } } } + +DOUBLE_BATTLE_TEST("Sandstorm deals damage based on turn order") +{ + GIVEN { + PLAYER(SPECIES_PHANPY); + PLAYER(SPECIES_WYNAUT) { Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + OPPONENT(SPECIES_WYNAUT) { Speed(3); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SANDSTORM); } + } SCENE { + NOT HP_BAR(playerLeft); + HP_BAR(opponentRight); + HP_BAR(opponentLeft); + HP_BAR(playerRight); + } +} diff --git a/test/battle/weather/snow.c b/test/battle/weather/snow.c index 3aceaf2f8..6c084f6b7 100644 --- a/test/battle/weather/snow.c +++ b/test/battle/weather/snow.c @@ -13,8 +13,8 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Snow multiplies the defense of Ice-types by 1.5x", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_SNOWSCAPE; } - PARAMETRIZE{ move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_GLALIE); diff --git a/test/species.c b/test/species.c index d4f0b4027..d09f1e3eb 100644 --- a/test/species.c +++ b/test/species.c @@ -1,4 +1,5 @@ #include "global.h" +#include "string_util.h" #include "test/test.h" #include "constants/form_change_types.h" @@ -138,3 +139,18 @@ TEST("No species has two evolutions that use the evolution tracker") EXPECT(evolutionTrackerEvolutions < 2); } + +extern const u8 gFallbackPokedexText[]; + +TEST("Every species has a description") +{ + u32 i; + u32 species = SPECIES_NONE; + for (i = 1; i < NUM_SPECIES; i++) + { + if (IsSpeciesEnabled(i)) + PARAMETRIZE { species = i; } + } + + EXPECT_NE(StringCompare(GetSpeciesPokedexDescription(species), gFallbackPokedexText), 0); +} diff --git a/test/test_runner.c b/test/test_runner.c index 2eda5e58d..4f36549d1 100644 --- a/test/test_runner.c +++ b/test/test_runner.c @@ -120,7 +120,6 @@ top: MoveSaveBlocks_ResetHeap(); ClearSav1(); ClearSav2(); - // ClearSav3(); gIntrTable[7] = Intr_Timer2; @@ -181,6 +180,7 @@ top: } Test_MgbaPrintf(":N%s", gTestRunnerState.test->name); + Test_MgbaPrintf(":L%s:%d", gTestRunnerState.test->filename); gTestRunnerState.result = TEST_RESULT_PASS; gTestRunnerState.expectedResult = TEST_RESULT_PASS; gTestRunnerState.expectLeaks = FALSE; @@ -218,6 +218,7 @@ top: // NOTE: Assumes that the compiler interns __FILE__. if (gTestRunnerState.skipFilename == gTestRunnerState.test->filename) // Assumption fails for tests in this file. { + Test_MgbaPrintf(":L%s:%d", gTestRunnerState.test->filename, gTestRunnerState.failedAssumptionsBlockLine); gTestRunnerState.result = TEST_RESULT_ASSUMPTION_FAIL; return; } @@ -482,9 +483,10 @@ static void Intr_Timer2(void) } } -void Test_ExitWithResult(enum TestResult result, const char *fmt, ...) +void Test_ExitWithResult(enum TestResult result, u32 stopLine, const char *fmt, ...) { gTestRunnerState.result = result; + gTestRunnerState.failedAssumptionsBlockLine = stopLine; ReinitCallbacks(); if (gTestRunnerState.state == STATE_REPORT_RESULT && gTestRunnerState.result != gTestRunnerState.expectedResult) @@ -692,3 +694,24 @@ void DACSHandle(void) ReinitCallbacks(); DACS_LR = ((uintptr_t)JumpToAgbMainLoop & ~1) + 4; } + +static const struct Test *GetTest(void) +{ + const struct Test *test = gTestRunnerState.test; + return test; +} + +u32 SourceLine(u32 sourceLineOffset) +{ + const struct Test *test = GetTest(); + return test->sourceLine + sourceLineOffset; +} + +u32 SourceLineOffset(u32 sourceLine) +{ + const struct Test *test = GetTest(); + if (sourceLine - test->sourceLine > 0xFF) + return 0; + else + return sourceLine - test->sourceLine; +} diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index 56edd3f5e..f27f858e5 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -27,10 +27,10 @@ #undef TestRunner_Battle_GetForcedAbility #endif -#define INVALID(fmt, ...) Test_ExitWithResult(TEST_RESULT_INVALID, "%s:%d: " fmt, gTestRunnerState.test->filename, sourceLine, ##__VA_ARGS__) -#define INVALID_IF(c, fmt, ...) do { if (c) Test_ExitWithResult(TEST_RESULT_INVALID, "%s:%d: " fmt, gTestRunnerState.test->filename, sourceLine, ##__VA_ARGS__); } while (0) +#define INVALID(fmt, ...) Test_ExitWithResult(TEST_RESULT_INVALID, sourceLine, ":L%s:%d: " fmt, gTestRunnerState.test->filename, sourceLine, ##__VA_ARGS__) +#define INVALID_IF(c, fmt, ...) do { if (c) Test_ExitWithResult(TEST_RESULT_INVALID, sourceLine, ":L%s:%d: " fmt, gTestRunnerState.test->filename, sourceLine, ##__VA_ARGS__); } while (0) -#define ASSUMPTION_FAIL_IF(c, fmt, ...) do { if (c) Test_ExitWithResult(TEST_RESULT_ASSUMPTION_FAIL, "%s:%d: " fmt, gTestRunnerState.test->filename, sourceLine, ##__VA_ARGS__); } while (0) +#define ASSUMPTION_FAIL_IF(c, fmt, ...) do { if (c) Test_ExitWithResult(TEST_RESULT_ASSUMPTION_FAIL, sourceLine, ":L%s:%d: " fmt, gTestRunnerState.test->filename, sourceLine, ##__VA_ARGS__); } while (0) #define STATE gBattleTestRunnerState #define DATA gBattleTestRunnerState->data @@ -147,21 +147,6 @@ static bool32 IsAITest(void) return FALSE; } -static u32 SourceLine(u32 sourceLineOffset) -{ - const struct BattleTest *test = GetBattleTest(); - return test->sourceLine + sourceLineOffset; -} - -static u32 SourceLineOffset(u32 sourceLine) -{ - const struct BattleTest *test = GetBattleTest(); - if (sourceLine - test->sourceLine > 0xFF) - return 0; - else - return sourceLine - test->sourceLine; -} - static u32 BattleTest_EstimateCost(void *data) { u32 cost; @@ -186,9 +171,9 @@ static void BattleTest_SetUp(void *data) InvokeTestFunction(test); STATE->parameters = STATE->parametersCount; if (STATE->parametersCount == 0 && test->resultsSize > 0) - Test_ExitWithResult(TEST_RESULT_INVALID, "results without PARAMETRIZE"); + Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":Lresults without PARAMETRIZE"); if (sizeof(*STATE) + test->resultsSize * STATE->parameters > sizeof(sBackupMapData)) - Test_ExitWithResult(TEST_RESULT_ERROR, "OOM: STATE (%d) + STATE->results (%d) too big for sBackupMapData (%d)", sizeof(*STATE), test->resultsSize * STATE->parameters, sizeof(sBackupMapData)); + Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LOOM: STATE (%d) + STATE->results (%d) too big for sBackupMapData (%d)", sizeof(*STATE), test->resultsSize * STATE->parameters, sizeof(sBackupMapData)); STATE->results = (void *)((char *)sBackupMapData + sizeof(struct BattleTestRunnerState)); memset(STATE->results, 0, test->resultsSize * STATE->parameters); switch (test->type) @@ -262,7 +247,7 @@ static void SetImplicitSpeeds(void) } } if (!madeProgress) - Test_ExitWithResult(TEST_RESULT_INVALID, "TURNs have contradictory speeds"); + Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":LTURNs have contradictory speeds"); } } @@ -336,9 +321,9 @@ static void BattleTest_Run(void *data) requiredOpponentPartySize = DATA.currentMonIndexes[i] + 1; } if (DATA.playerPartySize < requiredPlayerPartySize) - Test_ExitWithResult(TEST_RESULT_INVALID, "%d PLAYER Pokemon required", requiredPlayerPartySize); + Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":L%d PLAYER Pokemon required", requiredPlayerPartySize); if (DATA.opponentPartySize < requiredOpponentPartySize) - Test_ExitWithResult(TEST_RESULT_INVALID, "%d OPPONENT Pokemon required", requiredOpponentPartySize); + Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":L%d OPPONENT Pokemon required", requiredOpponentPartySize); for (i = 0; i < STATE->battlersCount; i++) PushBattlerAction(0, i, RECORDED_BYTE, 0xFF); @@ -348,7 +333,7 @@ static void BattleTest_Run(void *data) if (DATA.explicitSpeeds[B_SIDE_PLAYER] != (1 << DATA.playerPartySize) - 1 && DATA.explicitSpeeds[B_SIDE_OPPONENT] != (1 << DATA.opponentPartySize) - 1) { - Test_ExitWithResult(TEST_RESULT_INVALID, "Speed required for all PLAYERs and OPPONENTs"); + Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":LSpeed required for all PLAYERs and OPPONENTs"); } } else @@ -394,7 +379,7 @@ u32 RandomUniform(enum RandomTag tag, u32 lo, u32 hi) } else if (STATE->trials != n) { - Test_ExitWithResult(TEST_RESULT_ERROR, "RandomUniform called with inconsistent trials %d and %d", STATE->trials, n); + Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LRandomUniform called with inconsistent trials %d and %d", STATE->trials, n); } STATE->trialRatio = Q_4_12(1) / STATE->trials; return STATE->runTrial + lo; @@ -415,7 +400,7 @@ u32 RandomUniformExcept(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32 if (turn && turn->rng.tag == tag) { if (reject(turn->rng.value)) - Test_ExitWithResult(TEST_RESULT_INVALID, "WITH_RNG specified a rejected value (%d)", turn->rng.value); + Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":LWITH_RNG specified a rejected value (%d)", turn->rng.value); return turn->rng.value; } } @@ -436,7 +421,7 @@ u32 RandomUniformExcept(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32 while (reject(STATE->runTrial + lo + STATE->rngTrialOffset)) { if (STATE->runTrial + lo + STATE->rngTrialOffset > hi) - Test_ExitWithResult(TEST_RESULT_INVALID, "RandomUniformExcept called with inconsistent reject"); + Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":LRandomUniformExcept called with inconsistent reject"); STATE->rngTrialOffset++; } @@ -447,7 +432,7 @@ u32 RandomUniformExcept(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32 while (reject(default_)) { if (default_ == lo) - Test_ExitWithResult(TEST_RESULT_INVALID, "RandomUniformExcept rejected all values"); + Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":LRandomUniformExcept rejected all values"); default_--; } return default_; @@ -458,7 +443,7 @@ u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights) const struct BattlerTurn *turn = NULL; if (sum == 0) - Test_ExitWithResult(TEST_RESULT_ERROR, "RandomWeightedArray called with zero sum"); + Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LRandomWeightedArray called with zero sum"); if (gCurrentTurnActionNumber < gBattlersCount) { @@ -477,7 +462,7 @@ u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights) } else if (STATE->trials != n) { - Test_ExitWithResult(TEST_RESULT_ERROR, "RandomWeighted called with inconsistent trials %d and %d", STATE->trials, n); + Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LRandomWeighted called with inconsistent trials %d and %d", STATE->trials, n); } // TODO: Detect inconsistent sum. STATE->trialRatio = Q_4_12(weights[STATE->runTrial]) / sum; @@ -511,7 +496,7 @@ u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights) while (weights[n-1] == 0) { if (n == 1) - Test_ExitWithResult(TEST_RESULT_ERROR, "RandomWeightedArray called with all zero weights"); + Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LRandomWeightedArray called with all zero weights"); n--; } return n-1; @@ -537,7 +522,7 @@ const void *RandomElementArray(enum RandomTag tag, const void *array, size_t siz return (const u8 *)array + size * index; } // TODO: Incorporate the line number. - Test_ExitWithResult(TEST_RESULT_ERROR, "%s: RandomElement illegal value requested: %d", gTestRunnerState.test->filename, turn->rng.value); + Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":L%s: RandomElement illegal value requested: %d", gTestRunnerState.test->filename, turn->rng.value); } } @@ -550,7 +535,7 @@ const void *RandomElementArray(enum RandomTag tag, const void *array, size_t siz } else if (STATE->trials != count) { - Test_ExitWithResult(TEST_RESULT_ERROR, "RandomElement called with inconsistent trials %d and %d", STATE->trials, count); + Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LRandomElement called with inconsistent trials %d and %d", STATE->trials, count); } STATE->trialRatio = Q_4_12(1) / count; return (const u8 *)array + size * STATE->runTrial; @@ -601,7 +586,7 @@ void TestRunner_Battle_RecordAbilityPopUp(u32 battlerId, u32 ability) { const char *filename = gTestRunnerState.test->filename; u32 line = SourceLine(DATA.queuedEvents[match].sourceLineOffset); - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Matched ABILITY_POPUP", filename, line); + Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Matched ABILITY_POPUP", filename, line); } queuedEvent += event->groupSize; @@ -664,7 +649,7 @@ void TestRunner_Battle_RecordAnimation(u32 animType, u32 animId) { const char *filename = gTestRunnerState.test->filename; u32 line = SourceLine(DATA.queuedEvents[match].sourceLineOffset); - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Matched ANIMATION", filename, line); + Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Matched ANIMATION", filename, line); } queuedEvent += event->groupSize; @@ -754,7 +739,7 @@ void TestRunner_Battle_RecordHP(u32 battlerId, u32 oldHP, u32 newHP) { const char *filename = gTestRunnerState.test->filename; u32 line = SourceLine(DATA.queuedEvents[match].sourceLineOffset); - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Matched HP_BAR", filename, line); + Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Matched HP_BAR", filename, line); } queuedEvent += event->groupSize; @@ -809,10 +794,10 @@ void TestRunner_Battle_CheckChosenMove(u32 battlerId, u32 moveId, u32 target) bool32 movePasses = FALSE; if (expectedAction->type != B_ACTION_USE_MOVE) - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Expected %s, got MOVE", filename, expectedAction->sourceLine, sBattleActionNames[expectedAction->type]); + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Expected %s, got MOVE", filename, expectedAction->sourceLine, sBattleActionNames[expectedAction->type]); if (expectedAction->explicitTarget && expectedAction->target != target) - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Expected target %s, got %s", filename, expectedAction->sourceLine, BattlerIdentifier(expectedAction->target), BattlerIdentifier(target)); + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Expected target %s, got %s", filename, expectedAction->sourceLine, BattlerIdentifier(expectedAction->target), BattlerIdentifier(target)); for (i = 0; i < MAX_MON_MOVES; i++) { @@ -846,16 +831,16 @@ void TestRunner_Battle_CheckChosenMove(u32 battlerId, u32 moveId, u32 target) u32 moveSlot = GetMoveSlot(gBattleMons[battlerId].moves, moveId); PrintAiMoveLog(battlerId, moveSlot, moveId, gBattleStruct->aiFinalScore[battlerId][expectedAction->target][moveSlot]); if (countExpected > 1) - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Unmatched EXPECT_MOVES %S, got %S", filename, expectedAction->sourceLine, GetMoveName(expectedMoveId), GetMoveName(moveId)); + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Unmatched EXPECT_MOVES %S, got %S", filename, expectedAction->sourceLine, GetMoveName(expectedMoveId), GetMoveName(moveId)); else - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Unmatched EXPECT_MOVE %S, got %S", filename, expectedAction->sourceLine, GetMoveName(expectedMoveId), GetMoveName(moveId)); + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Unmatched EXPECT_MOVE %S, got %S", filename, expectedAction->sourceLine, GetMoveName(expectedMoveId), GetMoveName(moveId)); } if (expectedAction->notMove && !movePasses) { if (countExpected > 1) - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Unmatched NOT_EXPECT_MOVES %S", filename, expectedAction->sourceLine, GetMoveName(expectedMoveId)); + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Unmatched NOT_EXPECT_MOVES %S", filename, expectedAction->sourceLine, GetMoveName(expectedMoveId)); else - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Unmatched NOT_EXPECT_MOVE %S", filename, expectedAction->sourceLine, GetMoveName(expectedMoveId)); + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Unmatched NOT_EXPECT_MOVE %S", filename, expectedAction->sourceLine, GetMoveName(expectedMoveId)); } } // Turn passed, clear logs from the turn @@ -875,17 +860,17 @@ void TestRunner_Battle_CheckSwitch(u32 battlerId, u32 partyIndex) if (!expectedAction->pass) { if (expectedAction->type != B_ACTION_SWITCH) - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Expected %s, got SWITCH/SEND_OUT", filename, expectedAction->sourceLine, sBattleActionNames[expectedAction->type]); + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Expected %s, got SWITCH/SEND_OUT", filename, expectedAction->sourceLine, sBattleActionNames[expectedAction->type]); if (expectedAction->target != partyIndex) - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Expected partyIndex %d, got %d", filename, expectedAction->sourceLine, expectedAction->target, partyIndex); + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Expected partyIndex %d, got %d", filename, expectedAction->sourceLine, expectedAction->target, partyIndex); } DATA.aiActionsPlayed[battlerId]++; } void TestRunner_Battle_InvalidNoHPMon(u32 battlerId, u32 partyIndex) { - Test_ExitWithResult(TEST_RESULT_INVALID, "%s: INVALID: %s trying to send out a mon(id: %d) with 0 HP.", + Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":L%s: INVALID: %s trying to send out a mon(id: %d) with 0 HP.", gTestRunnerState.test->filename, BattlerIdentifier(battlerId), gBattlerPartyIndexes[battlerId]); } @@ -935,7 +920,7 @@ static void CheckIfMaxScoreEqualExpectMove(u32 battlerId, s32 target, struct Exp && (aiAction->moveSlots & gBitTable[i]) && !(aiAction->moveSlots & gBitTable[bestScoreId])) { - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_MOVE %S has the same best score(%d) as not expected MOVE %S", filename, + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: EXPECT_MOVE %S has the same best score(%d) as not expected MOVE %S", filename, aiAction->sourceLine, GetMoveName(moves[i]), scores[i], GetMoveName(moves[bestScoreId])); } // We DO NOT expect move 'i', but it has the same best score as another move. @@ -944,7 +929,7 @@ static void CheckIfMaxScoreEqualExpectMove(u32 battlerId, s32 target, struct Exp && (aiAction->moveSlots & gBitTable[i]) && !(aiAction->moveSlots & gBitTable[bestScoreId])) { - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: NOT_EXPECT_MOVE %S has the same best score(%d) as MOVE %S", filename, + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: NOT_EXPECT_MOVE %S has the same best score(%d) as MOVE %S", filename, aiAction->sourceLine, GetMoveName(moves[i]), scores[i], GetMoveName(moves[bestScoreId])); } } @@ -987,7 +972,7 @@ static void PrintAiMoveLog(u32 battlerId, u32 moveSlot, u32 moveId, s32 totalSco } if (scoreFromLogs != totalScore) { - Test_ExitWithResult(TEST_RESULT_ERROR, "Warning! Score from logs(%d) is different than actual score(%d). Make sure all of the score adjustments use the ADJUST_SCORE macro\n", scoreFromLogs, totalScore); + Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LWarning! Score from logs(%d) is different than actual score(%d). Make sure all of the score adjustments use the ADJUST_SCORE macro\n", scoreFromLogs, totalScore); } Test_MgbaPrintf("Total: %d\n", totalScore); } @@ -1025,7 +1010,7 @@ void TestRunner_Battle_CheckAiMoveScores(u32 battlerId) PrintAiMoveLog(battlerId, scoreCtx->moveSlot1, moveId1, scores[scoreCtx->moveSlot1]); if (!CheckComparision(scores[scoreCtx->moveSlot1], scoreCtx->value, scoreCtx->cmp)) { - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Unmatched SCORE_%s_VAL %S %d, got %d", + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Unmatched SCORE_%s_VAL %S %d, got %d", filename, scoreCtx->sourceLine, sCmpToStringTable[scoreCtx->cmp], GetMoveName(moveId1), scoreCtx->value, scores[scoreCtx->moveSlot1]); } } @@ -1036,7 +1021,7 @@ void TestRunner_Battle_CheckAiMoveScores(u32 battlerId) PrintAiMoveLog(battlerId, scoreCtx->moveSlot2, moveId2, scores[scoreCtx->moveSlot2]); if (!CheckComparision(scores[scoreCtx->moveSlot1], scores[scoreCtx->moveSlot2], scoreCtx->cmp)) { - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Unmatched SCORE_%s, got %S: %d, %S: %d", + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Unmatched SCORE_%s, got %S: %d, %S: %d", filename, scoreCtx->sourceLine, sCmpToStringTable[scoreCtx->cmp], GetMoveName(moveId1), scores[scoreCtx->moveSlot1], GetMoveName(moveId2), scores[scoreCtx->moveSlot2]); } } @@ -1136,7 +1121,7 @@ void TestRunner_Battle_RecordExp(u32 battlerId, u32 oldExp, u32 newExp) { const char *filename = gTestRunnerState.test->filename; u32 line = SourceLine(DATA.queuedEvents[match].sourceLineOffset); - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Matched EXPERIENCE_BAR", filename, line); + Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Matched EXPERIENCE_BAR", filename, line); } queuedEvent += event->groupSize; @@ -1225,7 +1210,7 @@ void TestRunner_Battle_RecordMessage(const u8 *string) { const char *filename = gTestRunnerState.test->filename; u32 line = SourceLine(DATA.queuedEvents[match].sourceLineOffset); - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Matched MESSAGE", filename, line); + Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Matched MESSAGE", filename, line); } queuedEvent += event->groupSize; @@ -1290,7 +1275,7 @@ void TestRunner_Battle_RecordStatus1(u32 battlerId, u32 status1) { const char *filename = gTestRunnerState.test->filename; u32 line = SourceLine(DATA.queuedEvents[match].sourceLineOffset); - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Matched STATUS_ICON", filename, line); + Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Matched STATUS_ICON", filename, line); } queuedEvent += event->groupSize; @@ -1325,7 +1310,7 @@ void TestRunner_Battle_AfterLastTurn(void) if (DATA.turns - 1 != DATA.lastActionTurn) { const char *filename = gTestRunnerState.test->filename; - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: %d TURNs specified, but %d ran", filename, SourceLine(0), DATA.turns, DATA.lastActionTurn + 1); + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: %d TURNs specified, but %d ran", filename, SourceLine(0), DATA.turns, DATA.lastActionTurn + 1); } while (DATA.queuedEvent < DATA.queuedEventsCount @@ -1338,7 +1323,7 @@ void TestRunner_Battle_AfterLastTurn(void) const char *filename = gTestRunnerState.test->filename; u32 line = SourceLine(DATA.queuedEvents[DATA.queuedEvent].sourceLineOffset); const char *macro = sEventTypeMacros[DATA.queuedEvents[DATA.queuedEvent].type]; - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: Unmatched %s", filename, line, macro); + Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Unmatched %s", filename, line, macro); } STATE->runThen = TRUE; @@ -1420,7 +1405,7 @@ static void CB2_BattleTest_NextTrial(void) if (abs(STATE->observedRatio - STATE->expectedRatio) <= Q_4_12(0.02)) gTestRunnerState.result = TEST_RESULT_PASS; else - Test_ExitWithResult(TEST_RESULT_FAIL, "Expected %q passes/successes, observed %q", STATE->expectedRatio, STATE->observedRatio); + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Expected %q passes/successes, observed %q", gTestRunnerState.test->filename, SourceLine(0), STATE->expectedRatio, STATE->observedRatio); } } @@ -1639,6 +1624,7 @@ void Ability_(u32 sourceLine, u32 ability) u32 species; const struct SpeciesInfo *info; INVALID_IF(!DATA.currentMon, "Ability outside of PLAYER/OPPONENT"); + INVALID_IF(ability >= ABILITIES_COUNT, "Illegal ability id: %d", ability); species = GetMonData(DATA.currentMon, MON_DATA_SPECIES); info = &gSpeciesInfo[species]; for (i = 0; i < NUM_ABILITY_SLOTS; i++) @@ -1875,7 +1861,7 @@ static void PushBattlerAction(u32 sourceLine, s32 battlerId, u32 actionType, u32 { u32 recordIndex = DATA.recordIndexes[battlerId]++; if (recordIndex >= BATTLER_RECORD_SIZE) - Test_ExitWithResult(TEST_RESULT_INVALID, "Too many actions"); + Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":LToo many actions"); DATA.battleRecordTypes[battlerId][recordIndex] = actionType; DATA.battleRecordSourceLineOffsets[battlerId][recordIndex] = SourceLineOffset(sourceLine); DATA.recordedBattle.battleRecord[battlerId][recordIndex] = byte; @@ -1897,10 +1883,10 @@ void TestRunner_Battle_CheckBattleRecordActionType(u32 battlerId, u32 recordInde && DATA.recordedBattle.battleRecord[battlerId][i-1] == B_ACTION_USE_MOVE) { u32 line = SourceLine(DATA.battleRecordSourceLineOffsets[battlerId][i-1]); - Test_ExitWithResult(TEST_RESULT_INVALID, "%s:%d: Illegal MOVE", filename, line); + Test_ExitWithResult(TEST_RESULT_INVALID, line, ":L%s:%d: Illegal MOVE", filename, line); } } - Test_ExitWithResult(TEST_RESULT_INVALID, "%s:%d: Illegal MOVE", filename, SourceLine(0)); + Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":L%s:%d: Illegal MOVE", filename, SourceLine(0)); } if (DATA.battleRecordTypes[battlerId][recordIndex] != RECORDED_BYTE) @@ -1928,13 +1914,13 @@ void TestRunner_Battle_CheckBattleRecordActionType(u32 battlerId, u32 recordInde switch (actionType) { case RECORDED_ACTION_TYPE: - Test_ExitWithResult(TEST_RESULT_INVALID, "%s:%d: Expected MOVE/SWITCH, got %s", filename, line, actualMacro); + Test_ExitWithResult(TEST_RESULT_INVALID, line, ":L%s:%d: Expected MOVE/SWITCH, got %s", filename, line, actualMacro); case RECORDED_PARTY_INDEX: - Test_ExitWithResult(TEST_RESULT_INVALID, "%s:%d: Expected SEND_OUT, got %s", filename, line, actualMacro); + Test_ExitWithResult(TEST_RESULT_INVALID, line, ":L%s:%d: Expected SEND_OUT, got %s", filename, line, actualMacro); } } - Test_ExitWithResult(TEST_RESULT_ERROR, "%s:%d: Illegal battle record", filename, line); + Test_ExitWithResult(TEST_RESULT_ERROR, line, ":L%s:%d: Illegal battle record", filename, line); } } else @@ -1942,7 +1928,7 @@ void TestRunner_Battle_CheckBattleRecordActionType(u32 battlerId, u32 recordInde if (DATA.lastActionTurn == gBattleResults.battleTurnCounter) { const char *filename = gTestRunnerState.test->filename; - Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: TURN %d incomplete", filename, SourceLine(0), gBattleResults.battleTurnCounter + 1); + Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: TURN %d incomplete", filename, SourceLine(0), gBattleResults.battleTurnCounter + 1); } } } @@ -1951,7 +1937,7 @@ void OpenTurn(u32 sourceLine) { INVALID_IF(DATA.turnState != TURN_CLOSED, "Nested TURN"); if (DATA.turns == MAX_TURNS) - Test_ExitWithResult(TEST_RESULT_ERROR, "%s:%d: TURN exceeds MAX_TURNS", gTestRunnerState.test->filename, sourceLine); + Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: TURN exceeds MAX_TURNS", gTestRunnerState.test->filename, sourceLine); DATA.turnState = TURN_OPEN; DATA.actionBattlers = 0x00; DATA.moveBattlers = 0x00; @@ -2483,7 +2469,7 @@ void QueueAbility(u32 sourceLine, struct BattlePokemon *battler, struct AbilityE s32 battlerId = battler - gBattleMons; INVALID_IF(!STATE->runScene, "ABILITY_POPUP outside of SCENE"); if (DATA.queuedEventsCount == MAX_QUEUED_EVENTS) - Test_ExitWithResult(TEST_RESULT_ERROR, "%s:%d: ABILITY exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine); + Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: ABILITY exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine); DATA.queuedEvents[DATA.queuedEventsCount++] = (struct QueuedEvent) { .type = QUEUED_ABILITY_POPUP_EVENT, .sourceLineOffset = SourceLineOffset(sourceLine), @@ -2503,7 +2489,7 @@ void QueueAnimation(u32 sourceLine, u32 type, u32 id, struct AnimationEventConte INVALID_IF(!STATE->runScene, "ANIMATION outside of SCENE"); if (DATA.queuedEventsCount == MAX_QUEUED_EVENTS) - Test_ExitWithResult(TEST_RESULT_ERROR, "%s:%d: ANIMATION exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine); + Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: ANIMATION exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine); attackerId = ctx.attacker ? ctx.attacker - gBattleMons : 0xF; if (type == ANIM_TYPE_MOVE) @@ -2538,7 +2524,7 @@ void QueueHP(u32 sourceLine, struct BattlePokemon *battler, struct HPEventContex INVALID_IF(!STATE->runScene, "HP_BAR outside of SCENE"); if (DATA.queuedEventsCount == MAX_QUEUED_EVENTS) - Test_ExitWithResult(TEST_RESULT_ERROR, "%s:%d: HP_BAR exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine); + Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: HP_BAR exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine); if (ctx.explicitHP) { @@ -2591,7 +2577,7 @@ void QueueExp(u32 sourceLine, struct BattlePokemon *battler, struct ExpEventCont INVALID_IF(!STATE->runScene, "EXPERIENCE_BAR outside of SCENE"); if (DATA.queuedEventsCount == MAX_QUEUED_EVENTS) - Test_ExitWithResult(TEST_RESULT_ERROR, "%s:%d: EXPERIENCE_BAR exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine); + Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: EXPERIENCE_BAR exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine); if (ctx.explicitExp) { @@ -2628,7 +2614,7 @@ void QueueMessage(u32 sourceLine, const u8 *pattern) { INVALID_IF(!STATE->runScene, "MESSAGE outside of SCENE"); if (DATA.queuedEventsCount == MAX_QUEUED_EVENTS) - Test_ExitWithResult(TEST_RESULT_ERROR, "%s:%d: MESSAGE exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine); + Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: MESSAGE exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine); DATA.queuedEvents[DATA.queuedEventsCount++] = (struct QueuedEvent) { .type = QUEUED_MESSAGE_EVENT, .sourceLineOffset = SourceLineOffset(sourceLine), @@ -2647,7 +2633,7 @@ void QueueStatus(u32 sourceLine, struct BattlePokemon *battler, struct StatusEve INVALID_IF(!STATE->runScene, "STATUS_ICON outside of SCENE"); if (DATA.queuedEventsCount == MAX_QUEUED_EVENTS) - Test_ExitWithResult(TEST_RESULT_ERROR, "%s:%d: STATUS_ICON exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine); + Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: STATUS_ICON exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine); if (ctx.none) mask = 0; @@ -2711,7 +2697,7 @@ struct AILogLine *GetLogLine(u32 battlerId, u32 moveIndex) } } - Test_ExitWithResult(TEST_RESULT_ERROR, "Too many AI log lines"); + Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LToo many AI log lines"); return NULL; } diff --git a/test/text.c b/test/text.c index e8eb4f7f2..0e6900eda 100644 --- a/test/text.c +++ b/test/text.c @@ -275,7 +275,7 @@ TEST("Item names fit on Shop Screen") TEST("Species names fit on Battle Screen HP box") { u32 i, genderWidthPx; - const u32 fontId = FONT_SMALL_NARROWER, widthPx = 54; + const u32 fontId = FONT_SMALL_NARROWER, widthPx = 55; u32 species = SPECIES_NONE; genderWidthPx = GetStringWidth(fontId, COMPOUND_STRING("♂"), 0); for (i = 1; i < NUM_SPECIES; i++) @@ -501,6 +501,25 @@ TEST("Species names fit on PokeNav Ribbon List Screen") EXPECT_LE(GetStringWidth(fontId, gSpeciesInfo[species].speciesName, 0), widthPx); } +TEST("Species names fit on Battle Screen HP box for vanilla mons with the default font") +{ + u32 i, genderWidthPx; + const u32 fontId = FONT_SMALL, widthPx = 55; + u32 species = SPECIES_NONE; + genderWidthPx = GetStringWidth(fontId, COMPOUND_STRING("♂"), 0); + for (i = 1; i < SPECIES_TURTWIG; i++) + { + if (IsSpeciesEnabled(i)) + { + PARAMETRIZE_LABEL("%S", gSpeciesInfo[i].speciesName) { species = i; } + } + } + if (gSpeciesInfo[i].genderRatio != MON_GENDERLESS) + EXPECT_LE(GetStringWidth(fontId, gSpeciesInfo[species].speciesName, 0) - genderWidthPx, widthPx); + else + EXPECT_LE(GetStringWidth(fontId, gSpeciesInfo[species].speciesName, 0), widthPx); +} + TEST("Ability names fit on Pokemon Summary Screen") { u32 i; diff --git a/tools/mgba-rom-test-hydra/main.c b/tools/mgba-rom-test-hydra/main.c index d4f39feb7..d9d8417e1 100644 --- a/tools/mgba-rom-test-hydra/main.c +++ b/tools/mgba-rom-test-hydra/main.c @@ -8,6 +8,7 @@ * * COMMANDS * N: Sets the test name to the remainder of the line. + * L: Sets the filename to the remainder of the line. * R: Sets the result to the remainder of the line, and flushes any * output buffered since the previous R. * P/K/F/A: Sets the result to the remaining of the line, flushes any @@ -46,6 +47,7 @@ struct Runner int outfd; char rom_path[FILENAME_MAX]; char test_name[256]; + char filename_line[256]; size_t input_buffer_size; size_t input_buffer_capacity; char *input_buffer; @@ -59,8 +61,12 @@ struct Runner int assumptionFails; int fails; int results; - char failedTestNames[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH]; - char knownFailingPassedTestNames[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH]; + char failed_TestNames[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH]; + char failed_TestFilenameLine[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH]; + char knownFailingPassed_TestNames[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH]; + char knownFailingPassed_FilenameLine[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH]; + char assumeFailed_TestNames[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; + char assumeFailed_FilenameLine[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; }; static unsigned nrunners = 0; @@ -102,6 +108,16 @@ static void handle_read(int i, struct Runner *runner) strncpy(runner->test_name, soc, eol - soc - 1); runner->test_name[eol - soc - 1] = '\0'; break; + case 'L': + soc += 2; + if (sizeof(runner->filename_line) <= eol - soc - 1) + { + fprintf(stderr, "filename_line too long\n"); + exit(2); + } + strncpy(runner->filename_line, soc, eol - soc - 1); + runner->filename_line[eol - soc - 1] = '\0'; + break; case 'P': runner->passes++; @@ -111,18 +127,29 @@ static void handle_read(int i, struct Runner *runner) goto add_to_results; case 'U': if (runner->knownFailsPassing < MAX_SUMMARY_TESTS_TO_LIST) - strcpy(runner->knownFailingPassedTestNames[runner->knownFailsPassing], runner->test_name); + { + strcpy(runner->knownFailingPassed_TestNames[runner->knownFailsPassing], runner->test_name); + strcpy(runner->knownFailingPassed_FilenameLine[runner->knownFailsPassing], runner->filename_line); + } runner->knownFailsPassing++; goto add_to_results; case 'T': runner->todos++; goto add_to_results; case 'A': + if (runner->assumptionFails < MAX_SUMMARY_TESTS_TO_LIST) + { + strcpy(runner->assumeFailed_TestNames[runner->assumptionFails], runner->test_name); + strcpy(runner->assumeFailed_FilenameLine[runner->assumptionFails], runner->filename_line); + } runner->assumptionFails++; goto add_to_results; case 'F': if (runner->fails < MAX_SUMMARY_TESTS_TO_LIST) - strcpy(runner->failedTestNames[runner->fails], runner->test_name); + { + strcpy(runner->failed_TestNames[runner->fails], runner->test_name); + strcpy(runner->failed_TestFilenameLine[runner->fails], runner->filename_line); + } runner->fails++; add_to_results: runner->results++; @@ -532,8 +559,14 @@ int main(int argc, char *argv[]) int fails = 0; int results = 0; - char failedTestNames[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; - char knownFailingPassedTestNames[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; + char failed_TestNames[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; + char failed_TestFilenameLine[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; + + char knownFailingPassed_TestNames[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; + char knownFailingPassed_FilenameLine[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; + + char assumeFailed_TestNames[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; + char assumeFailed_FilenameLine[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; for (int i = 0; i < nrunners; i++) { @@ -552,33 +585,43 @@ int main(int argc, char *argv[]) for (int j = 0; j < runners[i].knownFailsPassing; j++) { if (j < MAX_SUMMARY_TESTS_TO_LIST) - strcpy(knownFailingPassedTestNames[fails], runners[i].knownFailingPassedTestNames[j]); + { + strcpy(knownFailingPassed_TestNames[knownFailsPassing], runners[i].knownFailingPassed_TestNames[j]); + strcpy(knownFailingPassed_FilenameLine[knownFailsPassing], runners[i].knownFailingPassed_FilenameLine[j]); + } knownFailsPassing++; } todos += runners[i].todos; - assumptionFails += runners[i].assumptionFails; + for (int j = 0; j < runners[i].assumptionFails; j++) + { + if (j < MAX_SUMMARY_TESTS_TO_LIST) + { + strcpy(assumeFailed_TestNames[assumptionFails], runners[i].assumeFailed_TestNames[j]); + strcpy(assumeFailed_FilenameLine[assumptionFails], runners[i].assumeFailed_FilenameLine[j]); + } + assumptionFails++; + } for (int j = 0; j < runners[i].fails; j++) { if (j < MAX_SUMMARY_TESTS_TO_LIST) - strcpy(failedTestNames[fails], runners[i].failedTestNames[j]); + { + strcpy(failed_TestNames[fails], runners[i].failed_TestNames[j]); + strcpy(failed_TestFilenameLine[fails], runners[i].failed_TestFilenameLine[j]); + } fails++; } results += runners[i].results; } - qsort(failedTestNames, min(fails, MAX_SUMMARY_TESTS_TO_LIST), sizeof(char) * MAX_TEST_LIST_BUFFER_LENGTH, compare_strings); - qsort(knownFailingPassedTestNames, min(fails, MAX_SUMMARY_TESTS_TO_LIST), sizeof(char) * MAX_TEST_LIST_BUFFER_LENGTH, compare_strings); - if (results == 0) { fprintf(stdout, "\nNo tests found.\n"); } else { - fprintf(stdout, "\n"); if (fails > 0) { - fprintf(stdout, "- Tests \e[31mFAILED\e[0m : %d Add TESTS='X' to run tests with the defined prefix.\n", fails); + fprintf(stdout, "\n \e[31mFAILED\e[0m tests:\n"); for (int i = 0; i < fails; i++) { if (i >= MAX_SUMMARY_TESTS_TO_LIST) @@ -586,31 +629,51 @@ int main(int argc, char *argv[]) fprintf(stdout, " - \e[31mand %d more...\e[0m\n", fails - MAX_SUMMARY_TESTS_TO_LIST); break; } - fprintf(stdout, " - \e[31m%s\e[0m.\n", failedTestNames[i]); + fprintf(stdout, " - \e[31m%s\e[0m - %s.\n", failed_TestFilenameLine[i], failed_TestNames[i]); } } + + if (assumptionFails > 0) + { + fprintf(stdout, "\n Tests with \e[33mASSUMPTIONS_FAILED\e[0m:\n"); + for (int i = 0; i < assumptionFails; i++) + { + if (i >= MAX_SUMMARY_TESTS_TO_LIST) + { + fprintf(stdout, " - \e[33mand %d more...\e[0m\n", assumptionFails - MAX_SUMMARY_TESTS_TO_LIST); + break; + } + fprintf(stdout, " - \e[33m%s\e[0m - %s.\n", assumeFailed_FilenameLine[i], assumeFailed_TestNames[i]); + } + } + if (knownFailsPassing > 0) { - fprintf(stdout, "- \e[31mKNOWN_FAILING_PASSED\e[0m: %d \e[31mPlease remove KNOWN_FAILING if these tests intentionally PASS\e[0m\n", knownFailsPassing); + fprintf(stdout, "\n \e[33mKNOWN_FAILING\e[0m tests \e[32mPASSING\e[0m:\n"); for (int i = 0; i < knownFailsPassing; i++) { if (i >= MAX_SUMMARY_TESTS_TO_LIST) { - fprintf(stdout, " - \e[31mand %d more...\e[0m\n", knownFailsPassing - MAX_SUMMARY_TESTS_TO_LIST); + fprintf(stdout, " - \e[32mand %d more...\e[0m\n", knownFailsPassing - MAX_SUMMARY_TESTS_TO_LIST); break; } - fprintf(stdout, " - \e[31m%s\e[0m.\n", knownFailingPassedTestNames[i]); + fprintf(stdout, " - \e[32m%s\e[0m - %s.\n", knownFailingPassed_FilenameLine[i], knownFailingPassed_TestNames[i]); } } - fprintf(stdout, "- Tests \e[32mPASSED\e[0m: %d\n", passes); - if (knownFails > 0) - fprintf(stdout, "- Tests \e[33mKNOWN_FAILING\e[0m: %d\n", knownFails); - if (todos > 0) - fprintf(stdout, "- Tests \e[33mTO_DO\e[0m: %d\n", todos); - if (assumptionFails > 0) - fprintf(stdout, "- \e[33mASSUMPTIONS_FAILED\e[0m: %d\n", assumptionFails); - fprintf(stdout, "- Tests \e[34mTOTAL\e[0m: %d\n", results); + fprintf(stdout, "\n"); + if (fails > 0) + fprintf(stdout, "- Tests \e[31mFAILED\e[0m : %d Add TESTS='X' to run tests with the defined prefix.\n", fails); + if (knownFails > 0) + fprintf(stdout, "- Tests \e[33mKNOWN_FAILING\e[0m: %d\n", knownFails); + if (assumptionFails > 0) + fprintf(stdout, "- \e[33mASSUMPTIONS_FAILED\e[0m: %d\n", assumptionFails); + if (todos > 0) + fprintf(stdout, "- Tests \e[33mTO_DO\e[0m: %d\n", todos); + if (knownFailsPassing > 0) + fprintf(stdout, "- \e[32mKNOWN_FAILING_PASSING\e[0m: %d \e[33mPlease remove KNOWN_FAILING if these tests intentionally PASS\e[0m\n", knownFailsPassing); + fprintf(stdout, "- Tests \e[32mPASSED\e[0m: %d\n", passes); + fprintf(stdout, "- Tests \e[34mTOTAL\e[0m: %d\n", results); } fprintf(stdout, "\n");