From f7ec44c2ea44433811e469a1f53587c9adcc3d7b Mon Sep 17 00:00:00 2001 From: Nephrite Date: Tue, 6 Feb 2024 17:19:37 +0900 Subject: [PATCH] Fixed Shield Dust, added tests (#4137) * Fixed Shield Dust, added tests Also fixed a duplicate macro caused by near-simultaneous PR merges (oops) * Added KNOWN_FAILING Sparkling Aria test --------- Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- include/global.h | 59 ------------- include/metaprogram.h | 3 + src/battle_script_commands.c | 6 +- src/data/moves_info.h | 3 +- test/battle/ability/shield_dust.c | 139 ++++++++++++++++++++++++++++++ 5 files changed, 145 insertions(+), 65 deletions(-) create mode 100644 test/battle/ability/shield_dust.c diff --git a/include/global.h b/include/global.h index 18a8f8199c..b6d10b14ae 100644 --- a/include/global.h +++ b/include/global.h @@ -83,62 +83,6 @@ // Extracts the lower 16 bits of a 32-bit number #define LOHALF(n) ((n) & 0xFFFF) -/* (Credit to MGriffin) A rather monstrous way of finding the set bit in a word. -Invalid input causes a compiler error. Sample: https://cexplore.karathan.at/z/x1hm7B */ -#define BIT_INDEX(n) \ - (n) == (1 << 0) ? 0 : \ - (n) == (1 << 1) ? 1 : \ - (n) == (1 << 2) ? 2 : \ - (n) == (1 << 3) ? 3 : \ - (n) == (1 << 4) ? 4 : \ - (n) == (1 << 5) ? 5 : \ - (n) == (1 << 6) ? 6 : \ - (n) == (1 << 7) ? 7 : \ - (n) == (1 << 8) ? 8 : \ - (n) == (1 << 9) ? 9 : \ - (n) == (1 << 10) ? 10 : \ - (n) == (1 << 11) ? 11 : \ - (n) == (1 << 12) ? 12 : \ - (n) == (1 << 13) ? 13 : \ - (n) == (1 << 14) ? 14 : \ - (n) == (1 << 15) ? 15 : \ - (n) == (1 << 16) ? 16 : \ - (n) == (1 << 17) ? 17 : \ - (n) == (1 << 18) ? 18 : \ - (n) == (1 << 19) ? 19 : \ - (n) == (1 << 20) ? 20 : \ - (n) == (1 << 21) ? 21 : \ - (n) == (1 << 22) ? 22 : \ - (n) == (1 << 23) ? 23 : \ - (n) == (1 << 24) ? 24 : \ - (n) == (1 << 25) ? 25 : \ - (n) == (1 << 26) ? 26 : \ - (n) == (1 << 27) ? 27 : \ - (n) == (1 << 28) ? 28 : \ - (n) == (1 << 29) ? 29 : \ - (n) == (1 << 30) ? 30 : \ - (n) == (1 << 31) ? 31 : \ - *(u32 *)NULL - -#define COMPRESS_BITS_0 0, 1 -#define COMPRESS_BITS_1 1, 1 -#define COMPRESS_BITS_2 2, 1 -#define COMPRESS_BITS_3 3, 1 -#define COMPRESS_BITS_4 4, 1 -#define COMPRESS_BITS_5 5, 1 -#define COMPRESS_BITS_6 6, 1 -#define COMPRESS_BITS_7 7, 1 - -/* Will try and compress a set bit (or up to three sequential bits) into a single byte -Input must be of the form (upper << lower) where upper can be up to 3, lower up to 31 */ -#define COMPRESS_BITS(_val) COMPRESS_BITS_STEP_2 _val -#define COMPRESS_BITS_STEP_2(_unpacked) COMPRESS_BITS_STEP_3(COMPRESS_BITS_## _unpacked) -#define COMPRESS_BITS_STEP_3(...) COMPRESS_BITS_STEP_4(__VA_ARGS__) -#define COMPRESS_BITS_STEP_4(upper, lower) (((upper % 8) << 5) + (BIT_INDEX(lower))) - -/* Will read a compressed bit stored by COMPRESS_BIT into a single byte */ -#define UNCOMPRESS_BITS(compressed) ((compressed >> 5) << (compressed & 0x1F)) - // There are many quirks in the source code which have overarching behavioral differences from // a number of other files. For example, diploma.c seems to declare rodata before each use while // other files declare out of order and must be at the beginning. There are also a number of @@ -180,9 +124,6 @@ Input must be of the form (upper << lower) where upper can be up to 3, lower up #define NUM_FLAG_BYTES ROUND_BITS_TO_BYTES(FLAGS_COUNT) #define NUM_TRENDY_SAYING_BYTES ROUND_BITS_TO_BYTES(NUM_TRENDY_SAYINGS) -// Converts a string to a compound literal, essentially making it a pointer to const u8 -#define COMPOUND_STRING(str) (const u8[]) _(str) - // This produces an error at compile-time if expr is zero. // It looks like file.c:line: size of array `id' is negative #define STATIC_ASSERT(expr, id) typedef char id[(expr) ? 1 : -1]; diff --git a/include/metaprogram.h b/include/metaprogram.h index f0d6d9a81f..eee79b73b1 100644 --- a/include/metaprogram.h +++ b/include/metaprogram.h @@ -17,6 +17,9 @@ #define STR(...) STR_(__VA_ARGS__) #define STR_(...) #__VA_ARGS__ +/* Converts a string to a compound literal, essentially making it a pointer to const u8 */ +#define COMPOUND_STRING(str) (const u8[]) _(str) + /* Expands to the first/second/third/fourth argument. */ #define FIRST(a, ...) a #define SECOND(a, ...) __VA_OPT__(FIRST(__VA_ARGS__)) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 47246d9356..df023953f4 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2806,11 +2806,9 @@ void SetMoveEffect(bool32 primary, bool32 certain) // Just in case this flag is still set gBattleScripting.moveEffect &= ~MOVE_EFFECT_CERTAIN; - if ((battlerAbility == ABILITY_SHIELD_DUST - || GetBattlerHoldEffect(gEffectBattler, TRUE) == HOLD_EFFECT_COVERT_CLOAK) + if (!primary && affectsUser != MOVE_EFFECT_AFFECTS_USER && !(gHitMarker & HITMARKER_STATUS_ABILITY_EFFECT) - && !primary - && (gBattleScripting.moveEffect <= MOVE_EFFECT_TRI_ATTACK || gBattleScripting.moveEffect >= MOVE_EFFECT_SMACK_DOWN)) // Exclude stat lowering effects + && (battlerAbility == ABILITY_SHIELD_DUST || GetBattlerHoldEffect(gEffectBattler, TRUE) == HOLD_EFFECT_COVERT_CLOAK)) { if (battlerAbility == ABILITY_SHIELD_DUST) RecordAbilityBattle(gEffectBattler, battlerAbility); diff --git a/src/data/moves_info.h b/src/data/moves_info.h index f0abe00515..5fc35dba83 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -14969,9 +14969,8 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .ignoresSubstitute = TRUE, .metronomeBanned = TRUE, .sketchBanned = (B_SKETCH_BANS >= GEN_9), - .additionalEffects = ADDITIONAL_EFFECTS( + .additionalEffects = ADDITIONAL_EFFECTS({ // Feint move effect handled in script as it goes before animation - { .moveEffect = MOVE_EFFECT_DEF_MINUS_1, .self = TRUE, }), diff --git a/test/battle/ability/shield_dust.c b/test/battle/ability/shield_dust.c new file mode 100644 index 0000000000..4cd95de619 --- /dev/null +++ b/test/battle/ability/shield_dust.c @@ -0,0 +1,139 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Shield Dust blocks secondary effects") +{ + u16 move; + PARAMETRIZE { move = MOVE_NUZZLE; } + PARAMETRIZE { move = MOVE_INFERNO; } + PARAMETRIZE { move = MOVE_MORTAL_SPIN; } + PARAMETRIZE { move = MOVE_FAKE_OUT; } + PARAMETRIZE { move = MOVE_ROCK_TOMB; } + PARAMETRIZE { move = MOVE_SPIRIT_SHACKLE; } + PARAMETRIZE { move = MOVE_PSYCHIC_NOISE; } + + GIVEN { + ASSUME(MoveHasMoveEffectWithChance(MOVE_NUZZLE, MOVE_EFFECT_PARALYSIS, 100) == TRUE); + ASSUME(MoveHasMoveEffectWithChance(MOVE_INFERNO, MOVE_EFFECT_BURN, 100) == TRUE); + ASSUME(MoveHasMoveEffectWithChance(MOVE_MORTAL_SPIN, MOVE_EFFECT_POISON, 100) == TRUE); + ASSUME(MoveHasMoveEffectWithChance(MOVE_FAKE_OUT, MOVE_EFFECT_FLINCH, 100) == TRUE); + ASSUME(MoveHasMoveEffectWithChance(MOVE_ROCK_TOMB, MOVE_EFFECT_SPD_MINUS_1, 100) == TRUE); + ASSUME(MoveHasMoveEffectWithChance(MOVE_SPIRIT_SHACKLE, MOVE_EFFECT_PREVENT_ESCAPE, 100) == TRUE); + ASSUME(MoveHasMoveEffectWithChance(MOVE_PSYCHIC_NOISE, MOVE_EFFECT_PSYCHIC_NOISE, 100) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VIVILLON) { Ability(ABILITY_SHIELD_DUST); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + NONE_OF { + MESSAGE("Foe Vivillon is paralyzed! It may be unable to move!"); + MESSAGE("Foe Vivillon was burned!"); + MESSAGE("Foe Vivillon was poisoned!"); + MESSAGE("Foe Vivillon flinched!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Vivillon was prevented from healing!"); + } + } THEN { // Can't find good way to test trapping + EXPECT(!(opponent->status2 & STATUS2_ESCAPE_PREVENTION)); + } +} + +SINGLE_BATTLE_TEST("Shield Dust does not block primary effects") +{ + u16 move; + PARAMETRIZE { move = MOVE_INFESTATION; } + PARAMETRIZE { move = MOVE_THOUSAND_ARROWS; } + PARAMETRIZE { move = MOVE_JAW_LOCK; } + PARAMETRIZE { move = MOVE_PAY_DAY; } + + GIVEN { + ASSUME(MoveHasMoveEffectWithChance(MOVE_INFESTATION, MOVE_EFFECT_WRAP, 0) == TRUE); + ASSUME(MoveHasMoveEffectWithChance(MOVE_THOUSAND_ARROWS, MOVE_EFFECT_SMACK_DOWN, 0) == TRUE); + ASSUME(MoveHasMoveEffectWithChance(MOVE_JAW_LOCK, MOVE_EFFECT_TRAP_BOTH, 0) == TRUE); + ASSUME(MoveHasMoveEffectWithChance(MOVE_PAY_DAY, MOVE_EFFECT_PAYDAY, 0) == TRUE); + ASSUME(MoveHasMoveEffectWithChance(MOVE_SMACK_DOWN, MOVE_EFFECT_SMACK_DOWN, 0) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VIVILLON) { Ability(ABILITY_SHIELD_DUST); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + switch (move) + { + case MOVE_INFESTATION: + MESSAGE("Foe Vivillon has been afflicted with an infestation by Wobbuffet!"); + break; + case MOVE_THOUSAND_ARROWS: + MESSAGE("Foe Vivillon fell straight down!"); + break; + case MOVE_JAW_LOCK: + MESSAGE("Neither Pokémon can run away!"); + break; + case MOVE_PAY_DAY: + MESSAGE("Coins scattered everywhere!"); + break; + } + } THEN { // Can't find good way to test trapping + if (move == MOVE_JAW_LOCK) { + EXPECT(opponent->status2 & STATUS2_ESCAPE_PREVENTION); + EXPECT(player->status2 & STATUS2_ESCAPE_PREVENTION); + } + } +} + +SINGLE_BATTLE_TEST("Shield Dust does not block self-targeting effects, primary or secondary") +{ + u16 move; + PARAMETRIZE { move = MOVE_POWER_UP_PUNCH; } + PARAMETRIZE { move = MOVE_RAPID_SPIN; } + PARAMETRIZE { move = MOVE_LEAF_STORM; } + PARAMETRIZE { move = MOVE_METEOR_ASSAULT; } + + GIVEN { + ASSUME(MoveHasMoveEffectSelf(MOVE_POWER_UP_PUNCH, MOVE_EFFECT_ATK_PLUS_1) == TRUE); + ASSUME(MoveHasMoveEffectSelf(MOVE_RAPID_SPIN, MOVE_EFFECT_RAPIDSPIN) == TRUE); + ASSUME(MoveHasMoveEffectSelf(MOVE_LEAF_STORM, MOVE_EFFECT_SP_ATK_TWO_DOWN) == TRUE); + ASSUME(MoveHasMoveEffectSelf(MOVE_METEOR_ASSAULT, MOVE_EFFECT_RECHARGE) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VIVILLON) { Ability(ABILITY_SHIELD_DUST); } + } WHEN { + TURN { MOVE(player, move); } + if (move == MOVE_METEOR_ASSAULT) { + TURN { SKIP_TURN(player); } + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + switch (move) + { + case MOVE_POWER_UP_PUNCH: + case MOVE_RAPID_SPIN: + case MOVE_LEAF_STORM: + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + break; + case MOVE_METEOR_ASSAULT: // second turn + MESSAGE("Wobbuffet must recharge!"); + break; + } + } +} + +DOUBLE_BATTLE_TEST("Shield Dust does not block Sparkling Aria in doubles") +{ + KNOWN_FAILING; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VIVILLON) { Ability(ABILITY_SHIELD_DUST); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SPARKLING_ARIA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPARKLING_ARIA, playerLeft); + MESSAGE("Foe Vivillion's burn was healed."); + STATUS_ICON(opponentLeft, burn: TRUE); + } +}