From f5b149b971bddb3068a6e4b930ff0bb2a0c3cb31 Mon Sep 17 00:00:00 2001 From: Martin Griffin Date: Thu, 12 Oct 2023 11:14:07 +0100 Subject: [PATCH 1/3] Test improvements (#3408) * Run assumptions on all runners * Make i const in battle tests This avoids the pitfall of using i in a PARAMETRIZEd test and confusing the runner. --- include/test/battle.h | 12 ++++++------ test/battle/move_effect/salt_cure.c | 5 +++-- test/battle/status1/bad_poison.c | 16 +++++++++------- test/battle/status1/burn.c | 5 +++-- test/battle/status1/poison.c | 5 +++-- test/battle/status1/sleep.c | 6 +++--- test/test_runner.c | 2 +- 7 files changed, 28 insertions(+), 23 deletions(-) diff --git a/include/test/battle.h b/include/test/battle.h index e1b00efbdd..ead67ce320 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -472,8 +472,8 @@ enum { BATTLE_TEST_SINGLES, BATTLE_TEST_DOUBLES }; -typedef void (*SingleBattleTestFunction)(void *, u32, struct BattlePokemon *, struct BattlePokemon *); -typedef void (*DoubleBattleTestFunction)(void *, u32, struct BattlePokemon *, struct BattlePokemon *, struct BattlePokemon *, struct BattlePokemon *); +typedef void (*SingleBattleTestFunction)(void *, const u32, struct BattlePokemon *, struct BattlePokemon *); +typedef void (*DoubleBattleTestFunction)(void *, const u32, struct BattlePokemon *, struct BattlePokemon *, struct BattlePokemon *, struct BattlePokemon *); struct BattleTest { @@ -660,7 +660,7 @@ extern struct BattleTestRunnerState *gBattleTestRunnerState; #define SINGLE_BATTLE_TEST(_name, ...) \ struct CAT(Result, __LINE__) { MEMBERS(__VA_ARGS__) }; \ - static void CAT(Test, __LINE__)(struct CAT(Result, __LINE__) *, u32, struct BattlePokemon *, struct BattlePokemon *); \ + static void CAT(Test, __LINE__)(struct CAT(Result, __LINE__) *, const u32, struct BattlePokemon *, struct BattlePokemon *); \ __attribute__((section(".tests"))) static const struct Test CAT(sTest, __LINE__) = \ { \ .name = _name, \ @@ -674,11 +674,11 @@ extern struct BattleTestRunnerState *gBattleTestRunnerState; .resultsSize = sizeof(struct CAT(Result, __LINE__)), \ }, \ }; \ - static void CAT(Test, __LINE__)(struct CAT(Result, __LINE__) *results, u32 i, struct BattlePokemon *player, struct BattlePokemon *opponent) + static void CAT(Test, __LINE__)(struct CAT(Result, __LINE__) *results, const u32 i, struct BattlePokemon *player, struct BattlePokemon *opponent) #define DOUBLE_BATTLE_TEST(_name, ...) \ struct CAT(Result, __LINE__) { MEMBERS(__VA_ARGS__) }; \ - static void CAT(Test, __LINE__)(struct CAT(Result, __LINE__) *, u32, struct BattlePokemon *, struct BattlePokemon *, struct BattlePokemon *, struct BattlePokemon *); \ + static void CAT(Test, __LINE__)(struct CAT(Result, __LINE__) *, const u32, struct BattlePokemon *, struct BattlePokemon *, struct BattlePokemon *, struct BattlePokemon *); \ __attribute__((section(".tests"))) static const struct Test CAT(sTest, __LINE__) = \ { \ .name = _name, \ @@ -692,7 +692,7 @@ extern struct BattleTestRunnerState *gBattleTestRunnerState; .resultsSize = sizeof(struct CAT(Result, __LINE__)), \ }, \ }; \ - static void CAT(Test, __LINE__)(struct CAT(Result, __LINE__) *results, u32 i, struct BattlePokemon *playerLeft, struct BattlePokemon *opponentLeft, struct BattlePokemon *playerRight, struct BattlePokemon *opponentRight) + static void CAT(Test, __LINE__)(struct CAT(Result, __LINE__) *results, const u32 i, struct BattlePokemon *playerLeft, struct BattlePokemon *opponentLeft, struct BattlePokemon *playerRight, struct BattlePokemon *opponentRight) /* Parametrize */ diff --git a/test/battle/move_effect/salt_cure.c b/test/battle/move_effect/salt_cure.c index c24703fa26..b0b58c997c 100644 --- a/test/battle/move_effect/salt_cure.c +++ b/test/battle/move_effect/salt_cure.c @@ -8,18 +8,19 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Salt Cure inflicts 1/8 of the target's maximum HP as damage per turn") { + u32 j; GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_SALT_CURE); } - for (i = 0; i < 3; i++) + for (j = 0; j < 3; j++) TURN {} } SCENE { s32 maxHP = GetMonData(&OPPONENT_PARTY[0], MON_DATA_MAX_HP); ANIMATION(ANIM_TYPE_MOVE, MOVE_SALT_CURE, player); MESSAGE("Foe Wobbuffet is being salt cured!"); - for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SALT_CURE_DAMAGE, opponent); HP_BAR(opponent, damage: maxHP / 8); MESSAGE("Foe Wobbuffet is hurt by Salt Cure!"); diff --git a/test/battle/status1/bad_poison.c b/test/battle/status1/bad_poison.c index 7a430086bd..2ee3f05278 100644 --- a/test/battle/status1/bad_poison.c +++ b/test/battle/status1/bad_poison.c @@ -3,21 +3,23 @@ SINGLE_BATTLE_TEST("Bad poison deals 1/16th cumulative damage per turn") { + u32 j; GIVEN { PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_TOXIC_POISON); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - for (i = 0; i < 4; i++) + for (j = 0; j < 4; j++) TURN {} } SCENE { s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); - for (i = 0; i < 4; i++) - HP_BAR(player, damage: maxHP / 16 * (i + 1)); + for (j = 0; j < 4; j++) + HP_BAR(player, damage: maxHP / 16 * (j + 1)); } } SINGLE_BATTLE_TEST("Bad poison cumulative damage resets on switch") { + u32 j; GIVEN { PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_TOXIC_POISON); } PLAYER(SPECIES_WYNAUT); @@ -31,9 +33,9 @@ SINGLE_BATTLE_TEST("Bad poison cumulative damage resets on switch") TURN {} } SCENE { s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); - for (i = 0; i < 2; i++) - HP_BAR(player, damage: maxHP / 16 * (i + 1)); - for (i = 0; i < 2; i++) - HP_BAR(player, damage: maxHP / 16 * (i + 1)); + for (j = 0; j < 2; j++) + HP_BAR(player, damage: maxHP / 16 * (j + 1)); + for (j = 0; j < 2; j++) + HP_BAR(player, damage: maxHP / 16 * (j + 1)); } } diff --git a/test/battle/status1/burn.c b/test/battle/status1/burn.c index 84439ec9ef..77d58e5cc3 100644 --- a/test/battle/status1/burn.c +++ b/test/battle/status1/burn.c @@ -3,16 +3,17 @@ SINGLE_BATTLE_TEST("Burn deals 1/16th damage per turn") { + u32 j; GIVEN { ASSUME(B_BURN_DAMAGE >= GEN_LATEST); PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - for (i = 0; i < 4; i++) + for (j = 0; j < 4; j++) TURN {} } SCENE { s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); - for (i = 0; i < 4; i++) + for (j = 0; j < 4; j++) HP_BAR(player, damage: maxHP / 16); } } diff --git a/test/battle/status1/poison.c b/test/battle/status1/poison.c index 771d0d2eca..12f6ffa95f 100644 --- a/test/battle/status1/poison.c +++ b/test/battle/status1/poison.c @@ -3,15 +3,16 @@ SINGLE_BATTLE_TEST("Poison deals 1/8th damage per turn") { + u32 j; GIVEN { PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - for (i = 0; i < 4; i++) + for (j = 0; j < 4; j++) TURN {} } SCENE { s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); - for (i = 0; i < 4; i++) + for (j = 0; j < 4; j++) HP_BAR(player, damage: maxHP / 8); } } diff --git a/test/battle/status1/sleep.c b/test/battle/status1/sleep.c index 8ef6137d35..b3dd403eb1 100644 --- a/test/battle/status1/sleep.c +++ b/test/battle/status1/sleep.c @@ -3,7 +3,7 @@ SINGLE_BATTLE_TEST("Sleep prevents the battler from using a move") { - u32 turns; + u32 turns, j; PARAMETRIZE { turns = 1; } PARAMETRIZE { turns = 2; } PARAMETRIZE { turns = 3; } @@ -11,10 +11,10 @@ SINGLE_BATTLE_TEST("Sleep prevents the battler from using a move") PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP_TURN(turns)); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - for (i = 0; i < turns; i++) + for (j = 0; j < turns; j++) TURN { MOVE(player, MOVE_CELEBRATE); } } SCENE { - for (i = 0; i < turns - 1; i++) + for (j = 0; j < turns - 1; j++) MESSAGE("Wobbuffet is fast asleep."); MESSAGE("Wobbuffet woke up!"); STATUS_ICON(player, none: TRUE); diff --git a/test/test_runner.c b/test/test_runner.c index 1469fad1fd..7ca66ec081 100644 --- a/test/test_runner.c +++ b/test/test_runner.c @@ -88,7 +88,7 @@ static u32 AssignCostToRunner(void) u32 minCostProcess; if (gTestRunnerState.test->runner == &gAssumptionsRunner) - return 0; + return gTestRunnerI; minCostProcess = MinCostProcess(); From 231ebea3f0f9acd09c385d694e2de024c8e3e4c7 Mon Sep 17 00:00:00 2001 From: Bassoonian Date: Fri, 13 Oct 2023 20:50:48 +0200 Subject: [PATCH 2/3] Tests for 6 more abilities (#3399) * Tests for 6 more abilities * Fix most of Egg's comments * Update Purifying Salt and Leaf Guard * Parenthesis fixes --- data/battle_scripts_1.s | 1 + include/random.h | 1 + src/battle_main.c | 4 +- test/battle/ability/beast_boost.c | 43 ++++++++++++++++++ test/battle/ability/ice_scales.c | 35 +++++++++++++++ test/battle/ability/leaf_guard.c | 50 ++++++--------------- test/battle/ability/neuroforce.c | 30 +++++++++++++ test/battle/ability/purifying_salt.c | 65 ++++++++++++++++++++++++++++ test/battle/ability/quick_draw.c | 17 ++++++++ test/battle/ability/sharpness.c | 29 +++++++++++++ 10 files changed, 236 insertions(+), 39 deletions(-) create mode 100644 test/battle/ability/beast_boost.c create mode 100644 test/battle/ability/ice_scales.c create mode 100644 test/battle/ability/neuroforce.c create mode 100644 test/battle/ability/purifying_salt.c create mode 100644 test/battle/ability/quick_draw.c create mode 100644 test/battle/ability/sharpness.c diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 4381593505..57df1b4a2f 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -3853,6 +3853,7 @@ BattleScript_EffectRest:: jumpifuproarwakes BattleScript_RestCantSleep jumpifability BS_TARGET, ABILITY_INSOMNIA, BattleScript_InsomniaProtects jumpifability BS_TARGET, ABILITY_VITAL_SPIRIT, BattleScript_InsomniaProtects + jumpifability BS_ATTACKER, ABILITY_PURIFYING_SALT, BattleScript_InsomniaProtects .if B_LEAF_GUARD_PREVENTS_REST >= GEN_5 jumpifleafguardprotected BS_TARGET, BattleScript_LeafGuardPreventsRest .endif diff --git a/include/random.h b/include/random.h index db1dea3983..af3d7c75e5 100644 --- a/include/random.h +++ b/include/random.h @@ -92,6 +92,7 @@ enum RandomTag RNG_TRI_ATTACK, RNG_TRIPLE_ARROWS_DEFENSE_DOWN, RNG_TRIPLE_ARROWS_FLINCH, + RNG_QUICK_DRAW, }; #define RandomWeighted(tag, ...) \ diff --git a/src/battle_main.c b/src/battle_main.c index 8461873f05..0e88123959 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4745,7 +4745,7 @@ u32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMov // Battler 1 // Quick Draw - if (!ignoreChosenMoves && ability1 == ABILITY_QUICK_DRAW && !IS_MOVE_STATUS(gChosenMoveByBattler[battler1]) && Random() % 100 < 30) + if (!ignoreChosenMoves && ability1 == ABILITY_QUICK_DRAW && !IS_MOVE_STATUS(gChosenMoveByBattler[battler1]) && RandomPercentage(RNG_QUICK_DRAW, 30)) gProtectStructs[battler1].quickDraw = TRUE; // Quick Claw and Custap Berry if (!gProtectStructs[battler1].quickDraw @@ -4755,7 +4755,7 @@ u32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMov // Battler 2 // Quick Draw - if (!ignoreChosenMoves && ability2 == ABILITY_QUICK_DRAW && !IS_MOVE_STATUS(gChosenMoveByBattler[battler2]) && Random() % 100 < 30) + if (!ignoreChosenMoves && ability2 == ABILITY_QUICK_DRAW && !IS_MOVE_STATUS(gChosenMoveByBattler[battler2]) && RandomPercentage(RNG_QUICK_DRAW, 30)) gProtectStructs[battler2].quickDraw = TRUE; // Quick Claw and Custap Berry if (!gProtectStructs[battler2].quickDraw diff --git a/test/battle/ability/beast_boost.c b/test/battle/ability/beast_boost.c new file mode 100644 index 0000000000..da63bf9c4d --- /dev/null +++ b/test/battle/ability/beast_boost.c @@ -0,0 +1,43 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(P_GEN_7_POKEMON == TRUE); // Because only Ultra Beasts have this ability +} + +SINGLE_BATTLE_TEST("Beast Boost boosts the most proficient stat when knocking out a target") +{ + u8 stats[] = {1, 1, 1, 1, 1}; + PARAMETRIZE { stats[0] = 255; } + PARAMETRIZE { stats[1] = 255; } + PARAMETRIZE { stats[2] = 255; } + PARAMETRIZE { stats[3] = 255; } + PARAMETRIZE { stats[4] = 255; } + GIVEN { + PLAYER(SPECIES_NIHILEGO) { Ability(ABILITY_BEAST_BOOST); Attack(stats[0]); Defense(stats[1]); SpAttack(stats[2]); SpDefense(stats[3]); Speed(stats[4]); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_BEAST_BOOST); + switch(i) { + case 0: + MESSAGE("Nihilego's Beast Boost raised its Attack!"); + break; + case 1: + MESSAGE("Nihilego's Beast Boost raised its Defense!"); + break; + case 2: + MESSAGE("Nihilego's Beast Boost raised its Sp. Atk!"); + break; + case 3: + MESSAGE("Nihilego's Beast Boost raised its Sp. Def!"); + break; + case 4: + MESSAGE("Nihilego's Beast Boost raised its Speed!"); + break; + } + } +} diff --git a/test/battle/ability/ice_scales.c b/test/battle/ability/ice_scales.c new file mode 100644 index 0000000000..2bbe7dab05 --- /dev/null +++ b/test/battle/ability/ice_scales.c @@ -0,0 +1,35 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(P_GEN_8_POKEMON == TRUE); // Because only Frosmoth can have this ability. +} + +SINGLE_BATTLE_TEST("Ice Scales halves the damage from special moves", s16 damage) +{ + u32 move; + u16 ability; + PARAMETRIZE { ability = ABILITY_SHIELD_DUST; move = MOVE_PSYCHIC; } + PARAMETRIZE { ability = ABILITY_ICE_SCALES; move = MOVE_PSYCHIC; } + PARAMETRIZE { ability = ABILITY_SHIELD_DUST; move = MOVE_PSYSHOCK; } + PARAMETRIZE { ability = ABILITY_ICE_SCALES; move = MOVE_PSYSHOCK; } + PARAMETRIZE { ability = ABILITY_SHIELD_DUST; move = MOVE_TACKLE; } + PARAMETRIZE { ability = ABILITY_ICE_SCALES; move = MOVE_TACKLE; } + GIVEN { + ASSUME(gBattleMoves[MOVE_PSYCHIC].split == SPLIT_SPECIAL); + ASSUME(gBattleMoves[MOVE_PSYSHOCK].split == SPLIT_SPECIAL); + ASSUME(gBattleMoves[MOVE_PSYSHOCK].effect == EFFECT_PSYSHOCK); + ASSUME(gBattleMoves[MOVE_TACKLE].split == SPLIT_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_FROSMOTH) { Ability(ability); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage); // Ice Scales halves the damage of Psychic + EXPECT_MUL_EQ(results[2].damage, UQ_4_12(0.5), results[3].damage); // Ice Scales halves the damage of Psyshock, even if it targets Defense + EXPECT_EQ(results[4].damage, results[5].damage); // Ice Scales doesn't affect the damage of physical moves + } +} diff --git a/test/battle/ability/leaf_guard.c b/test/battle/ability/leaf_guard.c index 2c052b5a67..66eb6fc186 100644 --- a/test/battle/ability/leaf_guard.c +++ b/test/battle/ability/leaf_guard.c @@ -4,11 +4,12 @@ SINGLE_BATTLE_TEST("Leaf Guard prevents non-volatile status conditions in sun") { u32 move; - PARAMETRIZE { move = MOVE_WILL_O_WISP; } - PARAMETRIZE { move = MOVE_HYPNOSIS; } - PARAMETRIZE { move = MOVE_THUNDER_WAVE; } - PARAMETRIZE { move = MOVE_TOXIC; } - PARAMETRIZE { move = MOVE_POWDER_SNOW; } + u16 status; + PARAMETRIZE { move = MOVE_WILL_O_WISP; status = STATUS1_BURN; } + PARAMETRIZE { move = MOVE_HYPNOSIS; status = STATUS1_SLEEP; } + PARAMETRIZE { move = MOVE_THUNDER_WAVE; status = STATUS1_PARALYSIS; } + PARAMETRIZE { move = MOVE_TOXIC; status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { move = MOVE_POWDER_SNOW; status = STATUS1_FREEZE; } GIVEN { ASSUME(gBattleMoves[MOVE_WILL_O_WISP].effect == EFFECT_WILL_O_WISP); ASSUME(gBattleMoves[MOVE_HYPNOSIS].effect == EFFECT_SLEEP); @@ -20,39 +21,14 @@ SINGLE_BATTLE_TEST("Leaf Guard prevents non-volatile status conditions in sun") } WHEN { TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, move); } } SCENE { - switch (move) - { - case MOVE_WILL_O_WISP: - MESSAGE("Foe Wobbuffet used Will-o-Wisp!"); - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, opponent); - ABILITY_POPUP(player, ABILITY_LEAF_GUARD); - MESSAGE("It doesn't affect Leafeon…"); - break; - case MOVE_HYPNOSIS: - MESSAGE("Foe Wobbuffet used Hypnosis!"); - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPNOSIS, opponent); - ABILITY_POPUP(player, ABILITY_LEAF_GUARD); - MESSAGE("It doesn't affect Leafeon…"); - break; - case MOVE_THUNDER_WAVE: - MESSAGE("Foe Wobbuffet used Thunder Wave!"); - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, opponent); - ABILITY_POPUP(player, ABILITY_LEAF_GUARD); - MESSAGE("It doesn't affect Leafeon…"); - break; - case MOVE_TOXIC: - MESSAGE("Foe Wobbuffet used Toxic!"); - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, opponent); - ABILITY_POPUP(player, ABILITY_LEAF_GUARD); - MESSAGE("It doesn't affect Leafeon…"); - break; - case MOVE_POWDER_SNOW: - MESSAGE("Foe Wobbuffet used Powder Snow!"); - ANIMATION(ANIM_TYPE_MOVE, MOVE_POWDER_SNOW, opponent); - MESSAGE("It's super effective!"); - break; + if (move != MOVE_POWDER_SNOW) { + NOT ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ABILITY_POPUP(player, ABILITY_LEAF_GUARD); + MESSAGE("It doesn't affect Leafeon…"); + } else { + NOT ABILITY_POPUP(player, ABILITY_LEAF_GUARD); } - NONE_OF { STATUS_ICON(player, status1: TRUE); } + NOT STATUS_ICON(player, status); } } diff --git a/test/battle/ability/neuroforce.c b/test/battle/ability/neuroforce.c new file mode 100644 index 0000000000..df15bd4511 --- /dev/null +++ b/test/battle/ability/neuroforce.c @@ -0,0 +1,30 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(P_GEN_7_POKEMON == TRUE); // Because only Necrozma can have this ability. +} + +SINGLE_BATTLE_TEST("Neuroforce increases the strength of super-effective moves by 25%", s16 damage) +{ + u32 move; + u16 ability; + PARAMETRIZE { ability = ABILITY_NEUROFORCE; move = MOVE_SHADOW_BALL; } + PARAMETRIZE { ability = ABILITY_KLUTZ; move = MOVE_SHADOW_BALL; } + PARAMETRIZE { ability = ABILITY_NEUROFORCE; move = MOVE_TACKLE; } + PARAMETRIZE { ability = ABILITY_KLUTZ; move = MOVE_TACKLE; } + GIVEN { + ASSUME(gBattleMoves[MOVE_SHADOW_BALL].type == TYPE_GHOST); + ASSUME(gBattleMoves[MOVE_TACKLE].type == TYPE_NORMAL); + PLAYER(SPECIES_NECROZMA_ULTRA) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[1].damage, UQ_4_12(1.25), results[0].damage); // Neuroforce boosts the power of super-effective moves + EXPECT_EQ(results[2].damage, results[3].damage); // Neuroforce doesn't boost the power of other moves + } +} diff --git a/test/battle/ability/purifying_salt.c b/test/battle/ability/purifying_salt.c new file mode 100644 index 0000000000..0a53dbc61b --- /dev/null +++ b/test/battle/ability/purifying_salt.c @@ -0,0 +1,65 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Purifying Salt halves damage from Ghost-type moves", s16 damage) +{ + u16 ability; + PARAMETRIZE { ability = ABILITY_STURDY; } + PARAMETRIZE { ability = ABILITY_PURIFYING_SALT; } + GIVEN { + ASSUME(gBattleMoves[MOVE_SHADOW_BALL].type == TYPE_GHOST); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SHADOW_BALL); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Purifying Salt makes Rest fail") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_PURIFYING_SALT); HP(1); MaxHP(100);} + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REST); } + } SCENE { + NONE_OF { + MESSAGE("Wobbuffet went to sleep!"); + } + } +} + +SINGLE_BATTLE_TEST("Purifying Salt grants immunity to status effects") +{ + u32 move; + u16 status; + PARAMETRIZE { move = MOVE_WILL_O_WISP; status = STATUS1_BURN; } + PARAMETRIZE { move = MOVE_HYPNOSIS; status = STATUS1_SLEEP; } + PARAMETRIZE { move = MOVE_THUNDER_WAVE; status = STATUS1_PARALYSIS; } + PARAMETRIZE { move = MOVE_TOXIC; status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { move = MOVE_POWDER_SNOW; status = STATUS1_FREEZE; } + GIVEN { + ASSUME(gBattleMoves[MOVE_WILL_O_WISP].effect == EFFECT_WILL_O_WISP); + ASSUME(gBattleMoves[MOVE_HYPNOSIS].effect == EFFECT_SLEEP); + ASSUME(gBattleMoves[MOVE_THUNDER_WAVE].effect == EFFECT_PARALYZE); + ASSUME(gBattleMoves[MOVE_TOXIC].effect == EFFECT_TOXIC); + ASSUME(gBattleMoves[MOVE_POWDER_SNOW].effect == EFFECT_FREEZE_HIT); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_PURIFYING_SALT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + if (move != MOVE_POWDER_SNOW) { + NOT ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ABILITY_POPUP(player, ABILITY_PURIFYING_SALT); + MESSAGE("It doesn't affect Wobbuffet…"); + } else { + NOT ABILITY_POPUP(player, ABILITY_PURIFYING_SALT); + } + NOT STATUS_ICON(player, status); + } +} diff --git a/test/battle/ability/quick_draw.c b/test/battle/ability/quick_draw.c new file mode 100644 index 0000000000..7dd918e5d3 --- /dev/null +++ b/test/battle/ability/quick_draw.c @@ -0,0 +1,17 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Quick Draw has a 30% chance of going first") +{ + PASSES_RANDOMLY(3, 10, RNG_QUICK_DRAW); + GIVEN { + PLAYER(SPECIES_SLOWBRO_GALARIAN) { Ability(ABILITY_QUICK_DRAW); Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_QUICK_DRAW); + MESSAGE("Slowbro used Tackle!"); + MESSAGE("Foe Wobbuffet used Celebrate!"); + } +} diff --git a/test/battle/ability/sharpness.c b/test/battle/ability/sharpness.c new file mode 100644 index 0000000000..6360c4f8b3 --- /dev/null +++ b/test/battle/ability/sharpness.c @@ -0,0 +1,29 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(P_GEN_4_POKEMON == TRUE); // Because no Date: Fri, 13 Oct 2023 20:31:10 +0100 Subject: [PATCH 3/3] Move battle tests off the heap (#3414) --- gflib/malloc.c | 23 +++++++++++++++++++++++ include/fieldmap.h | 1 + include/test/battle.h | 2 +- src/fieldmap.c | 2 +- test/test_runner_battle.c | 35 +++++++++++++---------------------- 5 files changed, 39 insertions(+), 24 deletions(-) diff --git a/gflib/malloc.c b/gflib/malloc.c index dcfac6ee81..b3191e30b6 100644 --- a/gflib/malloc.c +++ b/gflib/malloc.c @@ -1,5 +1,8 @@ #include "global.h" #include "malloc.h" +#if TESTING +#include "test/test.h" +#endif static void *sHeapStart; static u32 sHeapSize; @@ -71,7 +74,27 @@ void *AllocInternal(void *heapStart, u32 size, const char *location) } if (pos->next == head) + { +#if TESTING + const struct MemBlock *head = HeapHead(); + const struct MemBlock *block = head; + do + { + if (block->allocated) + { + const char *location = MemBlockLocation(block); + if (location) + MgbaPrintf_("%s: %d bytes allocated", location, block->size); + else + MgbaPrintf_(": %d bytes allocated", block->size); + } + block = block->next; + } + while (block != head); + Test_ExitWithResult(TEST_RESULT_ERROR, "%s: OOM allocating %d bytes", location, size); +#endif return NULL; + } pos = pos->next; } diff --git a/include/fieldmap.h b/include/fieldmap.h index 47072bd1be..ecb1e49c4d 100644 --- a/include/fieldmap.h +++ b/include/fieldmap.h @@ -22,6 +22,7 @@ #include "main.h" extern struct BackupMapLayout gBackupMapLayout; +extern u16 ALIGNED(4) sBackupMapData[MAX_MAP_DATA_SIZE]; u32 MapGridGetMetatileIdAt(int, int); u32 MapGridGetMetatileBehaviorAt(int, int); diff --git a/include/test/battle.h b/include/test/battle.h index ead67ce320..e77482e40e 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -626,7 +626,7 @@ struct BattleTestRunnerState }; extern const struct TestRunner gBattleTestRunner; -extern struct BattleTestRunnerState *gBattleTestRunnerState; +extern struct BattleTestRunnerState *const gBattleTestRunnerState; #define MEMBERS(...) VARARG_8(MEMBERS_, __VA_ARGS__) #define MEMBERS_0() diff --git a/src/fieldmap.c b/src/fieldmap.c index 0ece73c5c3..8faab449a4 100644 --- a/src/fieldmap.c +++ b/src/fieldmap.c @@ -25,7 +25,7 @@ struct ConnectionFlags u8 east:1; }; -EWRAM_DATA static u16 ALIGNED(4) sBackupMapData[MAX_MAP_DATA_SIZE] = {0}; +EWRAM_DATA u16 ALIGNED(4) sBackupMapData[MAX_MAP_DATA_SIZE] = {0}; EWRAM_DATA struct MapHeader gMapHeader = {0}; EWRAM_DATA struct Camera gCamera = {0}; EWRAM_DATA static struct ConnectionFlags sMapConnectionFlags = {0}; diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index 96ddd2f385..d8369850d7 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -3,6 +3,7 @@ #include "battle_anim.h" #include "battle_controllers.h" #include "characters.h" +#include "fieldmap.h" #include "item_menu.h" #include "main.h" #include "malloc.h" @@ -33,7 +34,9 @@ #undef Q_4_12 #define Q_4_12(n) (s32)((n) * 4096) -EWRAM_DATA struct BattleTestRunnerState *gBattleTestRunnerState = NULL; +// Alias sBackupMapData to avoid using heap. +struct BattleTestRunnerState *const gBattleTestRunnerState = (void *)sBackupMapData; +STATIC_ASSERT(sizeof(struct BattleTestRunnerState) <= sizeof(sBackupMapData), sBackupMapDataSpace); static void CB2_BattleTest_NextParameter(void); static void CB2_BattleTest_NextTrial(void); @@ -122,9 +125,6 @@ static u32 BattleTest_EstimateCost(void *data) { u32 cost; const struct BattleTest *test = data; - STATE = AllocZeroed(sizeof(*STATE)); - if (!STATE) - return 0; STATE->runRandomly = TRUE; InvokeTestFunction(test); cost = 1; @@ -134,23 +134,21 @@ static u32 BattleTest_EstimateCost(void *data) cost *= 3; else if (STATE->trials > 1) cost *= STATE->trials; - FREE_AND_SET_NULL(STATE); return cost; } static void BattleTest_SetUp(void *data) { const struct BattleTest *test = data; - STATE = AllocZeroed(sizeof(*STATE)); - if (!STATE) - Test_ExitWithResult(TEST_RESULT_ERROR, "OOM: STATE = AllocZerod(%d)", sizeof(*STATE)); + memset(STATE, 0, sizeof(*STATE)); InvokeTestFunction(test); STATE->parameters = STATE->parametersCount; if (STATE->parametersCount == 0 && test->resultsSize > 0) Test_ExitWithResult(TEST_RESULT_INVALID, "results without PARAMETRIZE"); - STATE->results = AllocZeroed(test->resultsSize * STATE->parameters); - if (!STATE->results) - Test_ExitWithResult(TEST_RESULT_ERROR, "OOM: STATE->results = AllocZerod(%d)", sizeof(test->resultsSize * STATE->parameters)); + 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)); + STATE->results = (void *)((char *)sBackupMapData + sizeof(struct BattleTestRunnerState)); + memset(STATE->results, 0, test->resultsSize * STATE->parameters); switch (test->type) { case BATTLE_TEST_SINGLES: @@ -962,15 +960,10 @@ static void CB2_BattleTest_NextTrial(void) static void BattleTest_TearDown(void *data) { - if (STATE) - { - // Free resources that aren't cleaned up when the battle was - // aborted unexpectedly. - if (STATE->tearDownBattle) - TearDownBattle(); - FREE_AND_SET_NULL(STATE->results); - FREE_AND_SET_NULL(STATE); - } + // Free resources that aren't cleaned up when the battle was + // aborted unexpectedly. + if (STATE->tearDownBattle) + TearDownBattle(); } static bool32 BattleTest_CheckProgress(void *data) @@ -1843,8 +1836,6 @@ void QueueStatus(u32 sourceLine, struct BattlePokemon *battler, struct StatusEve void ValidateFinally(u32 sourceLine) { // Defer this error until after estimating the cost. - if (STATE->results == NULL) - return; INVALID_IF(STATE->parametersCount == 0, "FINALLY without PARAMETRIZE"); }