From 1fb42e44972ef77422a4efe9776d2d129417846e Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Mon, 7 Aug 2023 16:27:57 +0200 Subject: [PATCH] Sticky web / Court Change / Mirror Armor tests + small fixes (#3160) Co-authored-by: Eduardo Quezada D'Ottone Co-authored-by: Martin Griffin --- include/battle.h | 2 +- src/battle_main.c | 21 ++- src/battle_message.c | 4 +- src/battle_script_commands.c | 15 +- test/ability_mirror_armor.c | 202 +++++++++++++++++++++++++++ test/hold_effect_berserk_gene.c | 2 +- test/move_effect_court_change.c | 153 +++++++++++++++++++++ test/move_effect_defog.c | 2 +- test/move_effect_sticky_web.c | 237 ++++++++++++++++++++++++++++++++ 9 files changed, 620 insertions(+), 18 deletions(-) create mode 100644 test/ability_mirror_armor.c create mode 100644 test/move_effect_court_change.c create mode 100644 test/move_effect_sticky_web.c diff --git a/include/battle.h b/include/battle.h index b27062c120..e3f2e90d0f 100644 --- a/include/battle.h +++ b/include/battle.h @@ -212,6 +212,7 @@ struct SideTimer u8 toxicSpikesAmount; u8 stealthRockAmount; u8 stickyWebAmount; + u8 stickyWebBattlerId; u8 stickyWebBattlerSide; // Used for Court Change u8 auroraVeilTimer; u8 auroraVeilBattlerId; @@ -644,7 +645,6 @@ struct BattleStruct u8 forcedSwitch:4; // For each battler u8 switchInAbilityPostponed:4; // To not activate against an empty field, each bit for battler u8 ballSpriteIds[2]; // item gfx, window gfx - u8 stickyWebUser; u8 appearedInBattle; // Bitfield to track which Pokemon appeared in battle. Used for Burmy's form change u8 skyDropTargets[MAX_BATTLERS_COUNT]; // For Sky Drop, to account for if multiple Pokemon use Sky Drop in a double battle. // When using a move which hits multiple opponents which is then bounced by a target, we need to make sure, the move hits both opponents, the one with bounce, and the one without. diff --git a/src/battle_main.c b/src/battle_main.c index b75c44b777..855e770172 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3170,7 +3170,10 @@ static void BattleStartClearSetData(void) gBattleStruct->mega.triggerSpriteId = 0xFF; - gBattleStruct->stickyWebUser = 0xFF; + for (i = 0; i < ARRAY_COUNT(gSideTimers); i++) + { + gSideTimers[i].stickyWebBattlerId = 0xFF; + } gBattleStruct->appearedInBattle = 0; gBattleStruct->beatUpSlot = 0; @@ -3276,8 +3279,12 @@ void SwitchInClearSetData(void) gBattleStruct->lastMoveFailed &= ~(gBitTable[gActiveBattler]); gBattleStruct->palaceFlags &= ~(gBitTable[gActiveBattler]); - if (gActiveBattler == gBattleStruct->stickyWebUser) - gBattleStruct->stickyWebUser = 0xFF; // Switched into sticky web user slot so reset it + for (i = 0; i < ARRAY_COUNT(gSideTimers); i++) + { + // Switched into sticky web user slot, so reset stored battler ID + if (gSideTimers[i].stickyWebBattlerId == gActiveBattler) + gSideTimers[i].stickyWebBattlerId = 0xFF; + } for (i = 0; i < gBattlersCount; i++) { @@ -3390,8 +3397,12 @@ void FaintClearSetData(void) gBattleStruct->palaceFlags &= ~(gBitTable[gActiveBattler]); - if (gActiveBattler == gBattleStruct->stickyWebUser) - gBattleStruct->stickyWebUser = 0xFF; // User of sticky web fainted, so reset the stored battler ID + for (i = 0; i < ARRAY_COUNT(gSideTimers); i++) + { + // User of sticky web fainted, so reset the stored battler ID + if (gSideTimers[i].stickyWebBattlerId == gActiveBattler) + gSideTimers[i].stickyWebBattlerId = 0xFF; + } for (i = 0; i < gBattlersCount; i++) { diff --git a/src/battle_message.c b/src/battle_message.c index 32c4d7b041..307b539a5c 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -142,8 +142,8 @@ static const u8 sText_PkmnRaisedSpDefALittle[] = _("{B_ATK_PREFIX2}'s {B_CURRENT static const u8 sText_PkmnRaisedDef[] = _("{B_ATK_PREFIX2}'s {B_CURRENT_MOVE}\nraised DEFENSE!"); static const u8 sText_PkmnRaisedDefALittle[] = _("{B_ATK_PREFIX2}'s {B_CURRENT_MOVE}\nraised DEFENSE a little!"); static const u8 sText_PkmnCoveredByVeil[] = _("{B_ATK_PREFIX2}'s party is covered\nby a veil!"); -static const u8 sText_PkmnUsedSafeguard[] = _("{B_DEF_NAME_WITH_PREFIX}'s party is protected\nby SAFEGUARD!"); -static const u8 sText_PkmnSafeguardExpired[] = _("{B_ATK_PREFIX3}'s party is no longer\nprotected by SAFEGUARD!"); +static const u8 sText_PkmnUsedSafeguard[] = _("{B_DEF_NAME_WITH_PREFIX}'s party is protected\nby Safeguard!"); +static const u8 sText_PkmnSafeguardExpired[] = _("{B_ATK_PREFIX3}'s party is no longer\nprotected by Safeguard!"); static const u8 sText_PkmnWentToSleep[] = _("{B_ATK_NAME_WITH_PREFIX} went\nto sleep!"); static const u8 sText_PkmnSleptHealthy[] = _("{B_ATK_NAME_WITH_PREFIX} slept and\nbecame healthy!"); static const u8 sText_PkmnWhippedWhirlwind[] = _("{B_ATK_NAME_WITH_PREFIX} whipped\nup a whirlwind!"); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 8b69e36fb8..f1bb07281f 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -8760,8 +8760,9 @@ static bool32 IsTeatimeAffected(u32 battlerId) #define UPDATE_COURTCHANGED_BATTLER(structField)\ { \ - sideTimerPlayer->structField ^= BIT_SIDE; \ - sideTimerOpp->structField ^= BIT_SIDE; \ + temp = sideTimerPlayer->structField; \ + sideTimerPlayer->structField = BATTLE_OPPOSITE(sideTimerOpp->structField); \ + sideTimerOpp->structField = BATTLE_OPPOSITE(temp); \ } \ static bool32 CourtChangeSwapSideStatuses(void) @@ -8796,9 +8797,7 @@ static bool32 CourtChangeSwapSideStatuses(void) UPDATE_COURTCHANGED_BATTLER(auroraVeilBattlerId); UPDATE_COURTCHANGED_BATTLER(tailwindBattlerId); UPDATE_COURTCHANGED_BATTLER(luckyChantBattlerId); - - // For Mirror Armor only - gBattleStruct->stickyWebUser = gBattlerAttacker; + UPDATE_COURTCHANGED_BATTLER(stickyWebBattlerId); // Track which side originally set the Sticky Web SWAP(sideTimerPlayer->stickyWebBattlerSide, sideTimerOpp->stickyWebBattlerSide, temp); @@ -10830,8 +10829,8 @@ static void Cmd_various(void) // If Pokémon which set up Sticky Web is not on the field, no Pokémon have their Speed lowered." gBattlerAttacker = gBattlerTarget; // Initialize 'fail' condition SET_STATCHANGER(STAT_SPEED, 1, TRUE); - if (gBattleStruct->stickyWebUser != 0xFF) - gBattlerAttacker = gBattleStruct->stickyWebUser; + if (gSideTimers[GetBattlerSide(gActiveBattler)].stickyWebBattlerId != 0xFF) + gBattlerAttacker = gSideTimers[GetBattlerSide(gActiveBattler)].stickyWebBattlerId; break; } case VARIOUS_CUT_1_3_HP_RAISE_STATS: @@ -14092,9 +14091,9 @@ static void Cmd_setstickyweb(void) else { gSideStatuses[targetSide] |= SIDE_STATUS_STICKY_WEB; + gSideTimers[targetSide].stickyWebBattlerId = gBattlerAttacker; // For Mirror Armor gSideTimers[targetSide].stickyWebBattlerSide = GetBattlerSide(gBattlerAttacker); // For Court Change/Defiant - set this to the user's side gSideTimers[targetSide].stickyWebAmount = 1; - gBattleStruct->stickyWebUser = gBattlerAttacker; // For Mirror Armor gBattlescriptCurrInstr = cmd->nextInstr; } } diff --git a/test/ability_mirror_armor.c b/test/ability_mirror_armor.c new file mode 100644 index 0000000000..7e15e029b2 --- /dev/null +++ b/test/ability_mirror_armor.c @@ -0,0 +1,202 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(P_GEN_8_POKEMON == TRUE); +} + +SINGLE_BATTLE_TEST("Mirror Armor lowers a stat of the attacking pokemon") +{ + u16 move, statId; + + PARAMETRIZE { move = MOVE_LEER; statId = STAT_DEF; } + PARAMETRIZE { move = MOVE_GROWL; statId = STAT_ATK; } + PARAMETRIZE { move = MOVE_SWEET_SCENT; statId = STAT_EVASION; } + PARAMETRIZE { move = MOVE_SAND_ATTACK; statId = STAT_ACC; } + PARAMETRIZE { move = MOVE_CONFIDE; statId = STAT_SPATK; } + PARAMETRIZE { move = MOVE_FAKE_TEARS; statId = STAT_SPDEF; } + + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR);} + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + switch (statId) + { + case STAT_DEF: + MESSAGE("Foe Wynaut's Defense fell!"); + break; + case STAT_ATK: + MESSAGE("Foe Wynaut's Attack fell!"); + break; + case STAT_EVASION: + MESSAGE("Foe Wynaut's evasiveness harshly fell!"); + break; + case STAT_ACC: + MESSAGE("Foe Wynaut's accuracy fell!"); + break; + case STAT_SPATK: + MESSAGE("Foe Wynaut's Sp. Atk fell!"); + break; + case STAT_SPDEF: + MESSAGE("Foe Wynaut's Sp. Def harshly fell!"); + break; + } + } THEN { + EXPECT_EQ(player->statStages[statId], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[statId], (statId == STAT_SPDEF || statId == STAT_EVASION) ? DEFAULT_STAT_STAGE - 2 : DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor triggers even if the attacking Pokemon also has Mirror Armor ability") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); } + OPPONENT(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); } + } WHEN { + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("Foe Corviknigh used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Corviknigh's Defense fell!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor doesn't lower the stats of an attacking Pokemon with the Clear Body ability") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); } + OPPONENT(SPECIES_WYNAUT) { Ability(ABILITY_CLEAR_BODY); } + } WHEN { + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("Foe Wynaut used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + MESSAGE("Foe Wynaut's Clear Body prevents stat loss!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor lowers the Attack of Pokemon with Intimidate") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); } + OPPONENT(SPECIES_GYARADOS) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Gyarados's Attack fell!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + } +} + +// Unsure whether this should or should not fail, as Showdown has conflicting information. Needs testing in gen8 games. +SINGLE_BATTLE_TEST("Mirror Armor doesn't lower the stats of an attacking Pokemon behind Substitute") +{ + KNOWN_FAILING; + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); } + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("Foe Wynaut used Substitute!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + MESSAGE("Foe Wynaut used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor raises the stat of an attacking Pokemon with Contrary") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR);} + OPPONENT(SPECIES_SHUCKLE) {Ability(ABILITY_CONTRARY);} + } WHEN { + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("Foe Shuckle used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Shuckle's Defense rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor doesn't lower the stat of the attacking Pokemon if it is already at -6") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR);} + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_SCREECH); } + TURN { MOVE(player, MOVE_SCREECH); } + TURN { MOVE(player, MOVE_SCREECH); } + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("Corviknigh used Screech!"); + MESSAGE("Corviknigh used Screech!"); + MESSAGE("Corviknigh used Screech!"); + MESSAGE("Foe Wynaut used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Wynaut's Defense won't go lower!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], MIN_STAT_STAGE); + } +} + +// This behaviour needs to be verified in the actual games. Currently it's written to follow Showdown's logic. +DOUBLE_BATTLE_TEST("Mirror Armor lowers Speed of the partner Pokemon after Court Change was used by the opponent after it set up Sticky Web") +{ + KNOWN_FAILING; + GIVEN { + ASSUME(gBattleMoves[MOVE_STICKY_WEB].effect == EFFECT_STICKY_WEB); + ASSUME(gBattleMoves[MOVE_COURT_CHANGE].effect == EFFECT_COURT_CHANGE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_STICKY_WEB); } + TURN { MOVE(opponentLeft, MOVE_COURT_CHANGE); } + TURN { SWITCH(playerRight, 2);} + TURN { } + } SCENE { + MESSAGE("Wobbuffet used Sticky Web!"); + MESSAGE("Foe Wynaut used Court Change!"); + MESSAGE("Foe Wynaut swapped the battle effects affecting each side!"); + MESSAGE("Go! Corviknigh!"); + MESSAGE("Corviknigh was caught in a Sticky Web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Wobbuffet's Speed fell!"); + } +} diff --git a/test/hold_effect_berserk_gene.c b/test/hold_effect_berserk_gene.c index 8a473e8dac..981881e743 100644 --- a/test/hold_effect_berserk_gene.c +++ b/test/hold_effect_berserk_gene.c @@ -112,7 +112,7 @@ SINGLE_BATTLE_TEST("Berserk Gene does not confuse when Safeguard is active") } SCENE { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Using Berserk Gene, the Attack of Wobbuffet sharply rose!"); - MESSAGE("Wobbuffet's party is protected by SAFEGUARD!"); + MESSAGE("Wobbuffet's party is protected by Safeguard!"); NOT MESSAGE("Wobbuffet became confused!"); } } diff --git a/test/move_effect_court_change.c b/test/move_effect_court_change.c new file mode 100644 index 0000000000..9d03efddfa --- /dev/null +++ b/test/move_effect_court_change.c @@ -0,0 +1,153 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_COURT_CHANGE].effect == EFFECT_COURT_CHANGE); +} + +DOUBLE_BATTLE_TEST("Court Change swaps entry hazards used by the opponent") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); MOVE(opponentRight, MOVE_STEALTH_ROCK); } + TURN { MOVE(opponentLeft, MOVE_SPIKES); MOVE(opponentRight, MOVE_TOXIC_SPIKES); } + TURN { MOVE(playerLeft, MOVE_COURT_CHANGE); } + TURN { SWITCH(playerLeft, 2); SWITCH(opponentLeft, 2); } + } SCENE { + MESSAGE("Foe Wobbuffet used Sticky Web!"); + MESSAGE("Foe Wobbuffet used Stealth Rock!"); + MESSAGE("Foe Wobbuffet used Spikes!"); + MESSAGE("Foe Wobbuffet used Toxic Spikes!"); + MESSAGE("Wynaut used Court Change!"); + MESSAGE("Wynaut swapped the battle effects affecting each side!"); + MESSAGE("Go! Wynaut!"); + NONE_OF { + MESSAGE("Wynaut is hurt by spikes!"); + MESSAGE("Pointed stones dug into Wynaut!"); + MESSAGE("Wynaut was poisoned!"); + MESSAGE("Wynaut was caught in a Sticky Web!"); + } + MESSAGE("2 sent out Wobbuffet!"); + MESSAGE("Foe Wobbuffet is hurt by spikes!"); + MESSAGE("Pointed stones dug into Foe Wobbuffet!"); + MESSAGE("Foe Wobbuffet was poisoned!"); + MESSAGE("Foe Wobbuffet was caught in a Sticky Web!"); + } +} + +DOUBLE_BATTLE_TEST("Court Change swaps entry hazards used by the player") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_STICKY_WEB); MOVE(playerRight, MOVE_STEALTH_ROCK); } + TURN { MOVE(playerLeft, MOVE_SPIKES); MOVE(playerRight, MOVE_TOXIC_SPIKES); } + TURN { MOVE(opponentLeft, MOVE_COURT_CHANGE); } + TURN { SWITCH(opponentLeft, 2); SWITCH(playerLeft, 2); } + } SCENE { + MESSAGE("Wobbuffet used Sticky Web!"); + MESSAGE("Wobbuffet used Stealth Rock!"); + MESSAGE("Wobbuffet used Spikes!"); + MESSAGE("Wobbuffet used Toxic Spikes!"); + MESSAGE("Foe Wynaut used Court Change!"); + MESSAGE("Foe Wynaut swapped the battle effects affecting each side!"); + MESSAGE("Go! Wobbuffet!"); + MESSAGE("Wobbuffet is hurt by spikes!"); + MESSAGE("Pointed stones dug into Wobbuffet!"); + MESSAGE("Wobbuffet was poisoned!"); + MESSAGE("Wobbuffet was caught in a Sticky Web!"); + MESSAGE("2 sent out Wynaut!"); + NONE_OF { + MESSAGE("Foe Wynaut is hurt by spikes!"); + MESSAGE("Pointed stones dug into Foe Wynaut!"); + MESSAGE("Foe Wynaut was poisoned!"); + MESSAGE("Foe Wynaut was caught in a Sticky Web!"); + } + } +} + +DOUBLE_BATTLE_TEST("Court Change used by the player swaps Mist, Safeguard, Lucky Chant, Reflect, Light Screen, Tailwind") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_MIST); MOVE(opponentRight, MOVE_SAFEGUARD); } + TURN { MOVE(opponentLeft, MOVE_LUCKY_CHANT); MOVE(opponentRight, MOVE_REFLECT); } + TURN { MOVE(opponentLeft, MOVE_LIGHT_SCREEN); MOVE(opponentRight, MOVE_TAILWIND); } + TURN { MOVE(playerLeft, MOVE_COURT_CHANGE); } + TURN { } + TURN { } + TURN { } + TURN { } + } SCENE { + MESSAGE("Foe Wobbuffet used Mist!"); + MESSAGE("Foe Wobbuffet used Safeguard!"); + MESSAGE("Foe Wobbuffet used Lucky Chant!"); + MESSAGE("Foe Wobbuffet used Reflect!"); + MESSAGE("Foe Wobbuffet used Light Screen!"); + MESSAGE("Foe Wobbuffet used Tailwind!"); + MESSAGE("Wynaut used Court Change!"); + MESSAGE("Wynaut swapped the battle effects affecting each side!"); + // The effects now end for the player side. + MESSAGE("Ally's Mist wore off!"); + MESSAGE("Ally's party is no longer protected by Safeguard!"); + MESSAGE("Ally's Reflect wore off!"); + MESSAGE("Your team's Lucky Chant wore off!"); + MESSAGE("Your team's tailwind petered out!"); + MESSAGE("Ally's Light Screen wore off!"); + } +} + +DOUBLE_BATTLE_TEST("Court Change used by the opponent swaps Mist, Safeguard, Lucky Chant, Reflect, Light Screen, Tailwind") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_MIST); MOVE(playerRight, MOVE_SAFEGUARD); } + TURN { MOVE(playerLeft, MOVE_LUCKY_CHANT); MOVE(playerRight, MOVE_REFLECT); } + TURN { MOVE(playerLeft, MOVE_LIGHT_SCREEN); MOVE(playerRight, MOVE_TAILWIND); } + TURN { MOVE(opponentLeft, MOVE_COURT_CHANGE); } + TURN { } + TURN { } + TURN { } + TURN { } + } SCENE { + MESSAGE("Wobbuffet used Mist!"); + MESSAGE("Wobbuffet used Safeguard!"); + MESSAGE("Wobbuffet used Lucky Chant!"); + MESSAGE("Wobbuffet used Reflect!"); + MESSAGE("Wobbuffet used Light Screen!"); + MESSAGE("Wobbuffet used Tailwind!"); + MESSAGE("Foe Wynaut used Court Change!"); + MESSAGE("Foe Wynaut swapped the battle effects affecting each side!"); + // The effects now end for the player side. + MESSAGE("Foe's Mist wore off!"); + MESSAGE("Foe's party is no longer protected by Safeguard!"); + MESSAGE("Foe's Reflect wore off!"); + MESSAGE("The opposing team's Lucky Chant wore off!"); + MESSAGE("The opposing team's tailwind petered out!"); + MESSAGE("Foe's Light Screen wore off!"); + } +} diff --git a/test/move_effect_defog.c b/test/move_effect_defog.c index b55d5eab6f..055f3f61ae 100644 --- a/test/move_effect_defog.c +++ b/test/move_effect_defog.c @@ -125,7 +125,7 @@ DOUBLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes Mist and Safeguard STATUS_ICON(opponentRight, badPoison: TRUE); } else { - MESSAGE("Foe Wobbuffet's party is protected by SAFEGUARD!"); + MESSAGE("Foe Wobbuffet's party is protected by Safeguard!"); NOT STATUS_ICON(opponentRight, badPoison: TRUE); } } diff --git a/test/move_effect_sticky_web.c b/test/move_effect_sticky_web.c new file mode 100644 index 0000000000..f0a6eb8d83 --- /dev/null +++ b/test/move_effect_sticky_web.c @@ -0,0 +1,237 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_STICKY_WEB].effect == EFFECT_STICKY_WEB); +} + +SINGLE_BATTLE_TEST("Sticky Web lowers Speed by 1 on switch-in") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_STICKY_WEB); } + TURN { SWITCH(opponent, 1); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player); + MESSAGE("A sticky web spreads out on the ground around the opposing team!"); + MESSAGE("2 sent out Wynaut!"); + MESSAGE("Foe Wynaut was caught in a Sticky Web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Wynaut's Speed fell!"); + } +} + +SINGLE_BATTLE_TEST("Sticky Web can only be set up 1 time") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STICKY_WEB); } + TURN { MOVE(player, MOVE_STICKY_WEB); } + } SCENE { + MESSAGE("Wobbuffet used Sticky Web!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player); + MESSAGE("A sticky web spreads out on the ground around the opposing team!"); + + MESSAGE("Wobbuffet used Sticky Web!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player); + MESSAGE("But it failed!"); + } +} + + +DOUBLE_BATTLE_TEST("Sticky Web lowers Speed by 1 in a double battle after Explosion fainting both mons") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET) {Speed(5);} + PLAYER(SPECIES_WOBBUFFET) {HP(1500); Speed(10);} + PLAYER(SPECIES_WOBBUFFET) {Speed(10);} + OPPONENT(SPECIES_WOBBUFFET) {HP(1); Speed(1);} + OPPONENT(SPECIES_WOBBUFFET) {HP(1); Speed(1);} + OPPONENT(SPECIES_WYNAUT) {Speed(10);} + OPPONENT(SPECIES_WYNAUT) {Speed(10);} + } WHEN { + TURN { MOVE(playerRight, MOVE_STICKY_WEB); MOVE(playerLeft, MOVE_EXPLOSION); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 2); SEND_OUT(opponentRight, 3); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerRight); + 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("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!"); + } +} + +SINGLE_BATTLE_TEST("Sticky Web raises Speed by 1 for a Pokemon with Contrary") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SHUCKLE) { Ability(ABILITY_CONTRARY); } + } WHEN { + TURN { MOVE(player, MOVE_STICKY_WEB); } + TURN { SWITCH(opponent, 1); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player); + MESSAGE("A sticky web spreads out on the ground around the opposing team!"); + MESSAGE("2 sent out Shuckle!"); + MESSAGE("Foe Shuckle was caught in a Sticky Web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Shuckle's Speed rose!"); + } +} + +#define BATTLER_OPPONENT (opponentSetUpper == 0 ? opponentLeft : opponentRight) +#define BATTLER_PLAYER (playerSetUpper == 0 ? playerLeft : playerRight) + +DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - the battler which set up Sticky Web has its Speed lowered instead") +{ + u8 playerSetUpper, opponentSetUpper; // 0 left, 1 right + + PARAMETRIZE {playerSetUpper = 0; opponentSetUpper = 0; } + PARAMETRIZE {playerSetUpper = 0; opponentSetUpper = 1; } + PARAMETRIZE {playerSetUpper = 1; opponentSetUpper = 0; } + PARAMETRIZE {playerSetUpper = 1; opponentSetUpper = 1; } + + GIVEN { + ASSUME(P_GEN_8_POKEMON == TRUE); + PLAYER(SPECIES_SQUIRTLE); + PLAYER(SPECIES_CHARMANDER); + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web. + OPPONENT(SPECIES_CATERPIE); + OPPONENT(SPECIES_WEEDLE); + } WHEN { + TURN { MOVE(BATTLER_OPPONENT, MOVE_STICKY_WEB); } + TURN { MOVE(BATTLER_PLAYER, MOVE_STICKY_WEB); } + TURN { SWITCH(playerRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, BATTLER_OPPONENT); + MESSAGE("A sticky web spreads out on the ground around your team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, BATTLER_PLAYER); + MESSAGE("A sticky web spreads out on the ground around the opposing team!"); + + MESSAGE("Go! Corviknigh!"); + MESSAGE("Corviknigh was caught in a Sticky Web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, BATTLER_OPPONENT); + if (opponentSetUpper == 0) { + MESSAGE("Foe Caterpie's Speed fell!"); + } else { + MESSAGE("Foe Weedle's Speed fell!"); + } + } +} + +#undef BATTLER_OPPONENT +#undef BATTLER_PLAYER + +DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - no one has their Speed lowered if the set upper switched") +{ + u16 speedPlayer, speedOpponent; + + // We need to make sure Sticky Web user saves for both sides, so it doesn't matter who sets it first. + PARAMETRIZE { speedPlayer = 5; speedOpponent = 10; } + PARAMETRIZE { speedPlayer = 10; speedOpponent = 5; } + + GIVEN { + ASSUME(P_GEN_8_POKEMON == TRUE); + PLAYER(SPECIES_SQUIRTLE) { Speed(speedPlayer); } + PLAYER(SPECIES_CHARMANDER) { Speed(speedPlayer); } + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); Speed(speedOpponent); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web. + OPPONENT(SPECIES_CATERPIE) { Speed(speedOpponent); } + OPPONENT(SPECIES_WEEDLE) { Speed(speedOpponent); } + OPPONENT(SPECIES_PIDGEY) { Speed(speedOpponent); } // Flying type,so not affected by Sticky Web. + } WHEN { + TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); MOVE(playerRight, MOVE_STICKY_WEB); } + TURN { SWITCH(opponentLeft, 2); } + TURN { SWITCH(playerRight, 2); } + } SCENE { + if (speedPlayer > speedOpponent) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerRight); + MESSAGE("A sticky web spreads out on the ground around the opposing team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + MESSAGE("A sticky web spreads out on the ground around your team!"); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + MESSAGE("A sticky web spreads out on the ground around your team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerRight); + MESSAGE("A sticky web spreads out on the ground around the opposing team!"); + } + + MESSAGE("Go! Corviknigh!"); + MESSAGE("Corviknigh was caught in a Sticky Web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - no one has their Speed lowered if the set upper fainted") +{ + bool8 hasReplacement; + + // We need to make sure Sticky Web user saves for both sides, so it doesn't matter who sets it first. + PARAMETRIZE {hasReplacement = TRUE;} + PARAMETRIZE {hasReplacement = FALSE;} + + GIVEN { + ASSUME(P_GEN_8_POKEMON == TRUE); + ASSUME(gBattleMoves[MOVE_MEMENTO].effect == EFFECT_MEMENTO); + PLAYER(SPECIES_SQUIRTLE) {Speed(5); } + PLAYER(SPECIES_CHARMANDER) {Speed(5); } + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); Speed(5); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web. + OPPONENT(SPECIES_CATERPIE) {Speed(7); } + OPPONENT(SPECIES_WEEDLE) {Speed(7); } + if (hasReplacement) { + OPPONENT(SPECIES_PIDGEY) {Speed(7); } + } + + } WHEN { + TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); } + if (hasReplacement) { + TURN { MOVE(opponentLeft, MOVE_MEMENTO, target:playerLeft); SEND_OUT(opponentLeft, 2); } + } else { + TURN { MOVE(opponentLeft, MOVE_MEMENTO, target:playerLeft);} + } + TURN { SWITCH(playerRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + MESSAGE("A sticky web spreads out on the ground around your team!"); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEMENTO, opponentLeft); + MESSAGE("Foe Caterpie fainted!"); + if (hasReplacement) { + MESSAGE("2 sent out Pidgey!"); + } + + MESSAGE("Go! Corviknigh!"); + MESSAGE("Corviknigh was caught in a Sticky Web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } THEN { + if (hasReplacement) { + EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +}