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>
This commit is contained in:
Nephrite 2024-02-06 17:19:37 +09:00 committed by GitHub
parent c2c97d3c1c
commit f7ec44c2ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 145 additions and 65 deletions

View file

@ -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];

View file

@ -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__))

View file

@ -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);

View file

@ -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,
}),

View file

@ -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);
}
}