From c82f35d6bf649bb7930835c5be48ad4268005152 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Sun, 19 Feb 2023 12:01:13 +0100 Subject: [PATCH 1/8] correct Cmd_pickup --- src/battle_script_commands.c | 89 +++++++++++------------------------- test/ability_guts.c | 20 ++++++++ 2 files changed, 46 insertions(+), 63 deletions(-) create mode 100644 test/ability_guts.c diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 8b8d4f1e94..8e13ebf6db 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -14895,62 +14895,18 @@ static void Cmd_pickup(void) { CMD_ARGS(); - s32 i; - u16 species, heldItem; - u16 ability; + u32 i, j; + u16 species, heldItem, ability; u8 lvlDivBy10; - if (InBattlePike()) - { - - } - else if (InBattlePyramid()) + if (!InBattlePike()) // No items in Battle Pike. { + bool32 isInPyramid = InBattlePyramid_(); for (i = 0; i < PARTY_SIZE; i++) { species = GetMonData(&gPlayerParty[i], MON_DATA_SPECIES2); heldItem = GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM); - - ability = gSpeciesInfo[species].abilities[GetMonData(&gPlayerParty[i], MON_DATA_ABILITY_NUM)]; - - if (ability == ABILITY_PICKUP - && species != SPECIES_NONE - && species != SPECIES_EGG - && heldItem == ITEM_NONE - && (Random() % 10) == 0) - { - heldItem = GetBattlePyramidPickupItemId(); - SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); - } - else if (ability == ABILITY_HONEY_GATHER - && species != 0 - && species != SPECIES_EGG - && heldItem == ITEM_NONE) - { - if ((lvlDivBy10 + 1 ) * 5 > Random() % 100) - { - heldItem = ITEM_HONEY; - SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); - } - } - #if P_SHUCKLE_BERRY_JUICE == TRUE - else if (species == SPECIES_SHUCKLE - && heldItem == ITEM_ORAN_BERRY - && (Random() % 16) == 0) - { - heldItem = ITEM_BERRY_JUICE; - SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); - } - #endif - } - } - else - { - for (i = 0; i < PARTY_SIZE; i++) - { - species = GetMonData(&gPlayerParty[i], MON_DATA_SPECIES2); - heldItem = GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM); - lvlDivBy10 = (GetMonData(&gPlayerParty[i], MON_DATA_LEVEL)-1) / 10; //Moving this here makes it easier to add in abilities like Honey Gather + lvlDivBy10 = (GetMonData(&gPlayerParty[i], MON_DATA_LEVEL)-1) / 10; //Moving this here makes it easier to add in abilities like Honey Gather. if (lvlDivBy10 > 9) lvlDivBy10 = 9; @@ -14962,20 +14918,27 @@ static void Cmd_pickup(void) && heldItem == ITEM_NONE && (Random() % 10) == 0) { - s32 j; - s32 rand = Random() % 100; - - for (j = 0; j < (int)ARRAY_COUNT(sPickupProbabilities); j++) + if (isInPyramid) { - if (sPickupProbabilities[j] > rand) + heldItem = GetBattlePyramidPickupItemId(); + SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); + } + else + { + u32 rand = Random() % 100; + + for (j = 0; j < ARRAY_COUNT(sPickupProbabilities); j++) { - SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &sPickupItems[lvlDivBy10 + j]); - break; - } - else if (rand == 99 || rand == 98) - { - SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &sRarePickupItems[lvlDivBy10 + (99 - rand)]); - break; + if (sPickupProbabilities[j] > rand) + { + SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &sPickupItems[lvlDivBy10 + j]); + break; + } + else if (rand == 99 || rand == 98) + { + SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &sRarePickupItems[lvlDivBy10 + (99 - rand)]); + break; + } } } } @@ -14990,7 +14953,7 @@ static void Cmd_pickup(void) SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); } } - #if P_SHUCKLE_BERRY_JUICE == TRUE + #if P_SHUCKLE_BERRY_JUICE == TRUE else if (species == SPECIES_SHUCKLE && heldItem == ITEM_ORAN_BERRY && (Random() % 16) == 0) @@ -14998,7 +14961,7 @@ static void Cmd_pickup(void) heldItem = ITEM_BERRY_JUICE; SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &heldItem); } - #endif + #endif } } diff --git a/test/ability_guts.c b/test/ability_guts.c new file mode 100644 index 0000000000..259b863ec3 --- /dev/null +++ b/test/ability_guts.c @@ -0,0 +1,20 @@ +#include "global.h" +#include "test_battle.h" + +SINGLE_BATTLE_TEST("Blaze boosts Fire-type moves in a pinch", s16 damage) +{ + u16 hp; + PARAMETRIZE { hp = 99; } + PARAMETRIZE { hp = 33; } + GIVEN { + ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE); + PLAYER(SPECIES_CHARMANDER) { Ability(ABILITY_BLAZE); MaxHP(99); HP(hp); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_EMBER); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_GT(results[1].damage, results[0].damage); + } +} From dcb1117470bb809d1906f8b2b1f0f71da2cf6ff4 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Sun, 19 Feb 2023 12:01:47 +0100 Subject: [PATCH 2/8] a --- test/ability_guts.c | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 test/ability_guts.c diff --git a/test/ability_guts.c b/test/ability_guts.c deleted file mode 100644 index 259b863ec3..0000000000 --- a/test/ability_guts.c +++ /dev/null @@ -1,20 +0,0 @@ -#include "global.h" -#include "test_battle.h" - -SINGLE_BATTLE_TEST("Blaze boosts Fire-type moves in a pinch", s16 damage) -{ - u16 hp; - PARAMETRIZE { hp = 99; } - PARAMETRIZE { hp = 33; } - GIVEN { - ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE); - PLAYER(SPECIES_CHARMANDER) { Ability(ABILITY_BLAZE); MaxHP(99); HP(hp); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(player, MOVE_EMBER); } - } SCENE { - HP_BAR(opponent, captureDamage: &results[i].damage); - } FINALLY { - EXPECT_GT(results[1].damage, results[0].damage); - } -} From c33cc834bcaedea3552197322c07b849143e093f Mon Sep 17 00:00:00 2001 From: LOuroboros Date: Sun, 19 Feb 2023 19:04:36 -0300 Subject: [PATCH 3/8] Updated Psyshield Bash's accuracy --- src/data/battle_moves.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/battle_moves.h b/src/data/battle_moves.h index 415ef234ce..bf54142764 100644 --- a/src/data/battle_moves.h +++ b/src/data/battle_moves.h @@ -13294,7 +13294,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_Z] = .effect = EFFECT_DEFENSE_UP_HIT, .power = 70, .type = TYPE_PSYCHIC, - .accuracy = 100, + .accuracy = 90, .pp = 10, .secondaryEffectChance = 100, .target = MOVE_TARGET_SELECTED, From 53180ebf8791277339b829fc11eb26bb611180ad Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Mon, 20 Feb 2023 12:25:43 +0100 Subject: [PATCH 4/8] Update Healing wish to gen5 / gen8 mechanics (#2708) * Updated Healing Wish and added config * Fixed Lunar Dance message Co-authored-by: AgustinGDLV Co-authored-by: AgustinGDLV <103095241+AgustinGDLV@users.noreply.github.com> Co-authored-by: Martin Griffin --- asm/macros/battle_script.inc | 5 ++ data/battle_scripts_1.s | 19 +++-- include/battle.h | 2 + include/battle_scripts.h | 2 + include/config/battle.h | 2 + include/constants/battle_script_commands.h | 1 + src/battle_script_commands.c | 27 ++++++++ test/move_effect_healing_wish.c | 80 ++++++++++++++++++++++ test/move_effect_recoil_if_miss.c | 2 +- 9 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 test/move_effect_healing_wish.c diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 4f11fc78b8..6c985e3cdd 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1322,6 +1322,11 @@ .macro cancelmultiturnmoves battler:req various \battler, VARIOUS_CANCEL_MULTI_TURN_MOVES .endm + + @ Stores Healing Wish effect. + .macro storehealingwish battler:req + various \battler, VARIOUS_STORE_HEALING_WISH + .endm .macro setmagiccoattarget battler:req various \battler, VARIOUS_SET_MAGIC_COAT_TARGET diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index ae92aba7cf..6fa0cd7ad7 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -2697,6 +2697,8 @@ BattleScript_EffectHealingWish: instanthpdrop BS_ATTACKER setatkhptozero tryfaintmon BS_ATTACKER + storehealingwish BS_ATTACKER +.if B_HEALING_WISH_SWITCH <= GEN_4 openpartyscreen BS_ATTACKER, BattleScript_EffectHealingWishEnd switchoutabilities BS_ATTACKER waitstate @@ -2711,11 +2713,19 @@ BattleScript_EffectHealingWish: printstring STRINGID_SWITCHINMON switchinanim BS_ATTACKER, TRUE waitstate + switchineffects BS_ATTACKER +.endif +BattleScript_EffectHealingWishEnd: + moveendall + end + +BattleScript_HealingWishActivates:: setbyte cMULTISTRING_CHOOSER, 0 - jumpifnotchosenmove MOVE_LUNAR_DANCE, BattleScript_EffectHealingWishNewMon + goto BattleScript_EffectHealingWishRestore +BattleScript_LunarDanceActivates:: setbyte cMULTISTRING_CHOOSER, 1 restorepp BS_ATTACKER -BattleScript_EffectHealingWishNewMon: +BattleScript_EffectHealingWishRestore: printfromtable gHealingWishStringIds waitmessage B_WAIT_TIME_LONG playanimation BS_ATTACKER, B_ANIM_WISH_HEAL @@ -2730,10 +2740,7 @@ BattleScript_EffectHealingWishNewMon: waitstate printstring STRINGID_HEALINGWISHHEALED waitmessage B_WAIT_TIME_LONG - switchineffects BS_ATTACKER -BattleScript_EffectHealingWishEnd: - moveendall - end + return BattleScript_EffectWorrySeed: attackcanceler diff --git a/include/battle.h b/include/battle.h index 8be922384f..2c16887bf6 100644 --- a/include/battle.h +++ b/include/battle.h @@ -655,6 +655,8 @@ struct BattleStruct u16 overwrittenAbilities[MAX_BATTLERS_COUNT]; // abilities overwritten during battle (keep separate from battle history in case of switching) bool8 allowedToChangeFormInWeather[PARTY_SIZE][2]; // For each party member and side, used by Ice Face. u8 battleBondTransformed[NUM_BATTLE_SIDES]; // Bitfield for each party. + u8 storedHealingWish:4; // Each battler as a bit. + u8 storedLunarDance:4; // Each battler as a bit. }; #define F_DYNAMIC_TYPE_1 (1 << 6) diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 18c1911e52..70d398e2a5 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -452,6 +452,8 @@ extern const u8 BattleScript_MimicryActivates_End3[]; extern const u8 BattleScript_IceFaceNullsDamage[]; extern const u8 BattleScript_BattlerFormChangeWithStringEnd3[]; extern const u8 BattleScript_DampPreventsAftermath[]; +extern const u8 BattleScript_HealingWishActivates[]; +extern const u8 BattleScript_LunarDanceActivates[]; // zmoves extern const u8 BattleScript_ZMoveActivateDamaging[]; diff --git a/include/config/battle.h b/include/config/battle.h index 6dea72b406..a8f4a3e8b4 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -95,6 +95,8 @@ #define B_BEAT_UP GEN_LATEST // In Gen5+, Beat Up uses a different formula to calculate its damage, and deals Dark-type damage. Prior to Gen 5, each hit also announces the party member's name. #define B_DARK_VOID_FAIL GEN_LATEST // In Gen7+, only Darkrai can use Dark Void. #define B_BURN_HIT_THAW GEN_LATEST // In Gen6+, damaging moves with a chance of burn will thaw the target, regardless if they're fire-type moves or not. +#define B_HEALING_WISH_SWITCH GEN_LATEST // In Gen5+, the mon receiving Healing Wish is sent out at the end of the turn. + // Additionally, in gen8+ the Healing Wish's effect will be stored until the user switches into a statused or hurt mon. // Ability settings #define B_EXPANDED_ABILITY_NAMES TRUE // If TRUE, ability names are increased from 12 characters to 16 characters. diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 68e927d75f..9f18455a84 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -257,6 +257,7 @@ #define VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES 165 #define VARIOUS_JUMP_IF_NO_VALID_TARGETS 166 #define VARIOUS_JUMP_IF_EMERGENCY_EXITED 167 +#define VARIOUS_STORE_HEALING_WISH 168 // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 0 diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 8b8d4f1e94..48d23cf765 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6924,6 +6924,24 @@ static void Cmd_switchineffects(void) BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_SwitchInAbilityMsgRet; } + // Healing Wish activates before hazards. + // Starting from Gen8 - it heals only pokemon which can be healed. In gens 5,6,7 the effect activates anyways. + else if (((gBattleStruct->storedHealingWish & gBitTable[gActiveBattler]) || (gBattleStruct->storedLunarDance & gBitTable[gActiveBattler])) + && (gBattleMons[gActiveBattler].hp != gBattleMons[gActiveBattler].maxHP || gBattleMons[gActiveBattler].status1 != 0 || B_HEALING_WISH_SWITCH < GEN_8)) + { + if (gBattleStruct->storedHealingWish & gBitTable[gActiveBattler]) + { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_HealingWishActivates; + gBattleStruct->storedHealingWish &= ~(gBitTable[gActiveBattler]); + } + else // Lunar Dance + { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_LunarDanceActivates; + gBattleStruct->storedLunarDance &= ~(gBitTable[gActiveBattler]); + } + } else if (!(gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_SPIKES_DAMAGED) && (gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_SPIKES) && GetBattlerAbility(gActiveBattler) != ABILITY_MAGIC_GUARD @@ -11040,6 +11058,15 @@ static void Cmd_various(void) gBattlescriptCurrInstr = cmd->nextInstr; return; } + case VARIOUS_STORE_HEALING_WISH: + { + VARIOUS_ARGS(); + if (gCurrentMove == MOVE_LUNAR_DANCE) + gBattleStruct->storedLunarDance |= gBitTable[gActiveBattler]; + else + gBattleStruct->storedHealingWish |= gBitTable[gActiveBattler]; + break; + } } // End of switch (cmd->id) gBattlescriptCurrInstr = cmd->nextInstr; diff --git a/test/move_effect_healing_wish.c b/test/move_effect_healing_wish.c new file mode 100644 index 0000000000..98e8730b34 --- /dev/null +++ b/test/move_effect_healing_wish.c @@ -0,0 +1,80 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_HEALING_WISH].effect == EFFECT_HEALING_WISH); + ASSUME(gBattleMoves[MOVE_LUNAR_DANCE].effect == EFFECT_HEALING_WISH); +} + +#define TEST_MAX_HP (100) + +SINGLE_BATTLE_TEST("Healing Wish causes the user to faint and fully heals the replacement") +{ + GIVEN { + ASSUME(B_HEALING_WISH_SWITCH >= GEN_5); + PLAYER(SPECIES_GARDEVOIR); + PLAYER(SPECIES_WYNAUT) { HP(1); MaxHP(TEST_MAX_HP); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HEALING_WISH); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEALING_WISH, player); + HP_BAR(player, hp: 0); + MESSAGE("Gardevoir fainted!"); + MESSAGE("The healing wish came true for Wynaut!"); + HP_BAR(player, hp: TEST_MAX_HP); + STATUS_ICON(player, none: TRUE); + MESSAGE("Wynaut regained health!"); + } +} + +DOUBLE_BATTLE_TEST("Lunar Dance causes the user to faint and fully heals the replacement in a double battle") +{ + GIVEN { + ASSUME(B_HEALING_WISH_SWITCH >= GEN_5); + PLAYER(SPECIES_GARDEVOIR) { Speed(300); } + PLAYER(SPECIES_WOBBUFFET) { Speed(50); } + PLAYER(SPECIES_WYNAUT) { HP(TEST_MAX_HP - 1); MaxHP(TEST_MAX_HP); Status1(STATUS1_BURN); Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_LUNAR_DANCE); SEND_OUT(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LUNAR_DANCE, playerLeft); + HP_BAR(playerLeft, hp: 0); + MESSAGE("Gardevoir fainted!"); + MESSAGE("Wynaut became cloaked in mystical moonlight!"); + HP_BAR(playerLeft, hp: TEST_MAX_HP); + STATUS_ICON(playerLeft, none: TRUE); + MESSAGE("Wynaut regained health!"); + } +} + +SINGLE_BATTLE_TEST("Healing Wish effect activates only if the switched pokemon can be healed") +{ + GIVEN { + ASSUME(B_HEALING_WISH_SWITCH >= GEN_8); + PLAYER(SPECIES_GARDEVOIR) { Speed(300); } + PLAYER(SPECIES_NINJASK) { Speed(400); } + PLAYER(SPECIES_WYNAUT) { HP(TEST_MAX_HP / 2); MaxHP(TEST_MAX_HP); Status1(STATUS1_PARALYSIS); Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) {Speed(50); } + } WHEN { + TURN { MOVE(player, MOVE_HEALING_WISH); SEND_OUT(player, 1); } + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEALING_WISH, player); + HP_BAR(player, hp: 0); + MESSAGE("Gardevoir fainted!"); + NONE_OF { + MESSAGE("The healing wish came true for Wynaut!"); + MESSAGE("Wynaut regained health!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + MESSAGE("Do it! Wynaut!"); + MESSAGE("The healing wish came true for Wynaut!"); + HP_BAR(player, hp: TEST_MAX_HP); + STATUS_ICON(player, none: TRUE); + MESSAGE("Wynaut regained health!"); + } +} diff --git a/test/move_effect_recoil_if_miss.c b/test/move_effect_recoil_if_miss.c index 5c1f1a61af..8a80309d74 100644 --- a/test/move_effect_recoil_if_miss.c +++ b/test/move_effect_recoil_if_miss.c @@ -42,8 +42,8 @@ SINGLE_BATTLE_TEST("Jump Kick has 50% recoil on protect") SINGLE_BATTLE_TEST("Jump Kick has no recoil if no target") { - KNOWN_FAILING; // #2596. GIVEN { + ASSUME(B_HEALING_WISH_SWITCH >= GEN_5); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); From 2eb6401d52ab0853aa71e28f090ef8e1b1504f99 Mon Sep 17 00:00:00 2001 From: Martin Griffin Date: Sun, 19 Feb 2023 14:48:51 +0000 Subject: [PATCH 5/8] Test Overgrow, Swarm and Torrent --- test/ability_blaze.c | 2 +- test/ability_overgrow.c | 20 ++++++++++++++++++++ test/ability_swarm.c | 20 ++++++++++++++++++++ test/ability_torrent.c | 20 ++++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 test/ability_overgrow.c create mode 100644 test/ability_swarm.c create mode 100644 test/ability_torrent.c diff --git a/test/ability_blaze.c b/test/ability_blaze.c index 259b863ec3..1e12c6b294 100644 --- a/test/ability_blaze.c +++ b/test/ability_blaze.c @@ -15,6 +15,6 @@ SINGLE_BATTLE_TEST("Blaze boosts Fire-type moves in a pinch", s16 damage) } SCENE { HP_BAR(opponent, captureDamage: &results[i].damage); } FINALLY { - EXPECT_GT(results[1].damage, results[0].damage); + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); } } diff --git a/test/ability_overgrow.c b/test/ability_overgrow.c new file mode 100644 index 0000000000..82d8dd467e --- /dev/null +++ b/test/ability_overgrow.c @@ -0,0 +1,20 @@ +#include "global.h" +#include "test_battle.h" + +SINGLE_BATTLE_TEST("Overgrow boosts Grass-type moves in a pinch", s16 damage) +{ + u16 hp; + PARAMETRIZE { hp = 99; } + PARAMETRIZE { hp = 33; } + GIVEN { + ASSUME(gBattleMoves[MOVE_VINE_WHIP].type == TYPE_GRASS); + PLAYER(SPECIES_BULBASAUR) { Ability(ABILITY_OVERGROW); MaxHP(99); HP(hp); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_VINE_WHIP); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} diff --git a/test/ability_swarm.c b/test/ability_swarm.c new file mode 100644 index 0000000000..7709e976ea --- /dev/null +++ b/test/ability_swarm.c @@ -0,0 +1,20 @@ +#include "global.h" +#include "test_battle.h" + +SINGLE_BATTLE_TEST("Swarm boosts Bug-type moves in a pinch", s16 damage) +{ + u16 hp; + PARAMETRIZE { hp = 99; } + PARAMETRIZE { hp = 33; } + GIVEN { + ASSUME(gBattleMoves[MOVE_BUG_BITE].type == TYPE_BUG); + PLAYER(SPECIES_LEDYBA) { Ability(ABILITY_SWARM); MaxHP(99); HP(hp); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} diff --git a/test/ability_torrent.c b/test/ability_torrent.c new file mode 100644 index 0000000000..e5c701775d --- /dev/null +++ b/test/ability_torrent.c @@ -0,0 +1,20 @@ +#include "global.h" +#include "test_battle.h" + +SINGLE_BATTLE_TEST("Torrent boosts Water-type moves in a pinch", s16 damage) +{ + u16 hp; + PARAMETRIZE { hp = 99; } + PARAMETRIZE { hp = 33; } + GIVEN { + ASSUME(gBattleMoves[MOVE_BUBBLE].type == TYPE_WATER); + PLAYER(SPECIES_SQUIRTLE) { Ability(ABILITY_TORRENT); MaxHP(99); HP(hp); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BUBBLE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} From 666e59b37f2a3a79554589c8b7667928219a75e1 Mon Sep 17 00:00:00 2001 From: Martin Griffin Date: Sun, 19 Feb 2023 15:36:16 +0000 Subject: [PATCH 6/8] Test Roar The original implementation of forcerandomswitch was biased towards certain party members. --- src/battle_script_commands.c | 33 +++++------------ test/move_effect_roar.c | 70 ++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 25 deletions(-) create mode 100644 test/move_effect_roar.c diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 21dbf59e48..b4bafd215c 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -12089,8 +12089,8 @@ static void Cmd_forcerandomswitch(void) s32 lastMonId = 0; // + 1 s32 monsCount; struct Pokemon *party = NULL; - s32 validMons = 0; - s32 minNeeded; + u8 validMons[PARTY_SIZE]; + s32 validMonsCount = 0; bool32 redCardForcedSwitch = FALSE; @@ -12147,7 +12147,6 @@ static void Cmd_forcerandomswitch(void) firstMonId = 0; lastMonId = 6; monsCount = 6; - minNeeded = 2; battler2PartyId = gBattlerPartyIndexes[gBattlerTarget]; battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)]; } @@ -12166,7 +12165,6 @@ static void Cmd_forcerandomswitch(void) lastMonId = PARTY_SIZE / 2; } monsCount = PARTY_SIZE / 2; - minNeeded = 1; battler2PartyId = gBattlerPartyIndexes[gBattlerTarget]; battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)]; } @@ -12184,7 +12182,6 @@ static void Cmd_forcerandomswitch(void) lastMonId = PARTY_SIZE / 2; } monsCount = PARTY_SIZE / 2; - minNeeded = 1; battler2PartyId = gBattlerPartyIndexes[gBattlerTarget]; battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)]; } @@ -12195,7 +12192,6 @@ static void Cmd_forcerandomswitch(void) firstMonId = 0; lastMonId = PARTY_SIZE; monsCount = PARTY_SIZE; - minNeeded = 2; // since there are two opponents, it has to be a double battle } else { @@ -12210,7 +12206,6 @@ static void Cmd_forcerandomswitch(void) lastMonId = PARTY_SIZE / 2; } monsCount = PARTY_SIZE / 2; - minNeeded = 1; } battler2PartyId = gBattlerPartyIndexes[gBattlerTarget]; battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)]; @@ -12220,7 +12215,6 @@ static void Cmd_forcerandomswitch(void) firstMonId = 0; lastMonId = PARTY_SIZE; monsCount = PARTY_SIZE; - minNeeded = 2; battler2PartyId = gBattlerPartyIndexes[gBattlerTarget]; battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)]; } @@ -12229,7 +12223,6 @@ static void Cmd_forcerandomswitch(void) firstMonId = 0; lastMonId = PARTY_SIZE; monsCount = PARTY_SIZE; - minNeeded = 1; battler2PartyId = gBattlerPartyIndexes[gBattlerTarget]; // there is only one pokemon out in single battles battler1PartyId = gBattlerPartyIndexes[gBattlerTarget]; } @@ -12238,13 +12231,15 @@ static void Cmd_forcerandomswitch(void) { if (GetMonData(&party[i], MON_DATA_SPECIES) != SPECIES_NONE && !GetMonData(&party[i], MON_DATA_IS_EGG) - && GetMonData(&party[i], MON_DATA_HP) != 0) + && GetMonData(&party[i], MON_DATA_HP) != 0 + && i != battler1PartyId + && i != battler2PartyId) { - validMons++; + validMons[validMonsCount++] = i; } } - if (!redCardForcedSwitch && validMons <= minNeeded) + if (validMonsCount == 0) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -12252,19 +12247,7 @@ static void Cmd_forcerandomswitch(void) { *(gBattleStruct->battlerPartyIndexes + gBattlerTarget) = gBattlerPartyIndexes[gBattlerTarget]; gBattlescriptCurrInstr = BattleScript_RoarSuccessSwitch; - - do - { - i = Random() % monsCount; - i += firstMonId; - } - while (i == battler2PartyId - || i == battler1PartyId - || GetMonData(&party[i], MON_DATA_SPECIES) == SPECIES_NONE - || GetMonData(&party[i], MON_DATA_IS_EGG) == TRUE - || GetMonData(&party[i], MON_DATA_HP) == 0); - - *(gBattleStruct->monToSwitchIntoId + gBattlerTarget) = i; + *(gBattleStruct->monToSwitchIntoId + gBattlerTarget) = validMons[Random() % validMonsCount]; if (!IsMultiBattle()) SwitchPartyOrder(gBattlerTarget); diff --git a/test/move_effect_roar.c b/test/move_effect_roar.c new file mode 100644 index 0000000000..2d4eadda85 --- /dev/null +++ b/test/move_effect_roar.c @@ -0,0 +1,70 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_ROAR].effect == EFFECT_ROAR); +} + +SINGLE_BATTLE_TEST("Roar switches the target with a random non-fainted replacement") +{ + PASSES_RANDOMLY(1, 2); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(player, MOVE_ROAR); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROAR, player); + MESSAGE("Foe Bulbasaur was dragged out!"); + } +} + +DOUBLE_BATTLE_TEST("Roar switches the target with a random non-battler, non-fainted replacement") +{ + PASSES_RANDOMLY(1, 2); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_ROAR, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROAR, playerLeft); + MESSAGE("Foe Bulbasaur was dragged out!"); + } +} + +SINGLE_BATTLE_TEST("Roar fails if no replacements") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROAR); } + } SCENE { + MESSAGE("Wobbuffet used Roar!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Roar fails if replacements fainted") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { HP(0); } + } WHEN { + TURN { MOVE(player, MOVE_ROAR); } + } SCENE { + MESSAGE("Wobbuffet used Roar!"); + MESSAGE("But it failed!"); + } +} From afe09e96532165c044848ace9a3a3ed034567041 Mon Sep 17 00:00:00 2001 From: Martin Griffin Date: Sun, 19 Feb 2023 21:08:30 +0000 Subject: [PATCH 7/8] Test Dragon Tail Remove the "But it failed!" message if the foe cannot be switched. --- data/battle_scripts_1.s | 7 ++- include/battle_scripts.h | 1 - src/battle_script_commands.c | 2 +- test/move_effect_hit_switch_target.c | 72 ++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 test/move_effect_hit_switch_target.c diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 6fa0cd7ad7..53925b578d 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -2128,9 +2128,9 @@ BattleScript_EffectHitSwitchTarget: moveendall jumpifability BS_TARGET, ABILITY_SUCTION_CUPS, BattleScript_AbilityPreventsPhasingOut jumpifstatus3 BS_TARGET, STATUS3_ROOTED, BattleScript_PrintMonIsRooted - tryhitswitchtarget BattleScript_EffectHitSwitchTargetMoveEnd -BattleScript_EffectHitSwitchTargetMoveEnd: - end + tryhitswitchtarget BattleScript_MoveEnd + forcerandomswitch BattleScript_MoveEnd + goto BattleScript_MoveEnd BattleScript_EffectClearSmog: setmoveeffect MOVE_EFFECT_CLEAR_SMOG @@ -3708,7 +3708,6 @@ BattleScript_EffectRoar:: accuracycheck BattleScript_ButItFailed, NO_ACC_CALC_CHECK_LOCK_ON accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE jumpifbattletype BATTLE_TYPE_ARENA, BattleScript_ButItFailed -BattleScript_ForceRandomSwitch:: forcerandomswitch BattleScript_ButItFailed BattleScript_EffectMultiHit:: diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 70d398e2a5..d4e4d4b3ce 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -293,7 +293,6 @@ extern const u8 BattleScript_WishMegaEvolution[]; extern const u8 BattleScript_MoveEffectRecoilWithStatus[]; extern const u8 BattleScript_EffectWithChance[]; extern const u8 BattleScript_MoveEffectClearSmog[]; -extern const u8 BattleScript_ForceRandomSwitch[]; extern const u8 BattleScript_SideStatusWoreOffReturn[]; extern const u8 BattleScript_MoveEffectSmackDown[]; extern const u8 BattleScript_MoveEffectFlameBurst[]; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index b4bafd215c..fdd3d4f07b 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -9792,7 +9792,7 @@ static void Cmd_various(void) && GetBattlerAbility(gBattlerTarget) != ABILITY_GUARD_DOG) { gBattleScripting.switchCase = B_SWITCH_HIT; - gBattlescriptCurrInstr = BattleScript_ForceRandomSwitch; + gBattlescriptCurrInstr = cmd->nextInstr; } else { diff --git a/test/move_effect_hit_switch_target.c b/test/move_effect_hit_switch_target.c new file mode 100644 index 0000000000..9c50a4e4c0 --- /dev/null +++ b/test/move_effect_hit_switch_target.c @@ -0,0 +1,72 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_DRAGON_TAIL].effect == EFFECT_HIT_SWITCH_TARGET); + ASSUME(gBattleMoves[MOVE_LOCK_ON].effect == EFFECT_LOCK_ON); +} + +SINGLE_BATTLE_TEST("Dragon Tail switches the target with a random non-fainted replacement") +{ + KNOWN_FAILING; // Only 18/50. Waiting for an improved PASSES_RANDOMLY. + PASSES_RANDOMLY(90 * 1, 100 * 2); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + MESSAGE("Foe Bulbasaur was dragged out!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Tail switches the target with a random non-battler, non-fainted replacement") +{ + PASSES_RANDOMLY(90 * 1, 100 * 2); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_DRAGON_TAIL, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, playerLeft); + MESSAGE("Foe Bulbasaur was dragged out!"); + } +} + +SINGLE_BATTLE_TEST("Dragon Tail does not fail if no replacements") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + NOT MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Dragon Tail does not fail if replacements fainted") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { HP(0); } + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + NOT MESSAGE("But it failed!"); + } +} From 29c64c82f329c45131c7e31e7e48ad69ae3fdb97 Mon Sep 17 00:00:00 2001 From: Martin Griffin Date: Tue, 21 Feb 2023 10:44:58 +0000 Subject: [PATCH 8/8] Test Red Card Dragon Tail activates Red Card if the target does not switch. --- asm/macros/battle_script.inc | 4 + data/battle_scripts_1.s | 7 +- include/battle.h | 1 + include/constants/battle_script_commands.h | 1 + src/battle_script_commands.c | 10 +- test/hold_effect_red_card.c | 378 +++++++++++++++++++++ 6 files changed, 399 insertions(+), 2 deletions(-) create mode 100644 test/hold_effect_red_card.c diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 6c985e3cdd..7c94f06ffe 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -2247,3 +2247,7 @@ various \battler, VARIOUS_JUMP_IF_EMERGENCY_EXITED .4byte \jumpInstr .endm + + .macro hitswitchtargetfailed + various 0, VARIOUS_HIT_SWITCH_TARGET_FAILED + .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 53925b578d..239d221093 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -2129,7 +2129,12 @@ BattleScript_EffectHitSwitchTarget: jumpifability BS_TARGET, ABILITY_SUCTION_CUPS, BattleScript_AbilityPreventsPhasingOut jumpifstatus3 BS_TARGET, STATUS3_ROOTED, BattleScript_PrintMonIsRooted tryhitswitchtarget BattleScript_MoveEnd - forcerandomswitch BattleScript_MoveEnd + forcerandomswitch BattleScript_HitSwitchTargetForceRandomSwitchFailed + goto BattleScript_MoveEnd + +BattleScript_HitSwitchTargetForceRandomSwitchFailed: + hitswitchtargetfailed + setbyte sSWITCH_CASE, B_SWITCH_NORMAL goto BattleScript_MoveEnd BattleScript_EffectClearSmog: diff --git a/include/battle.h b/include/battle.h index 2c16887bf6..c3281174d0 100644 --- a/include/battle.h +++ b/include/battle.h @@ -651,6 +651,7 @@ struct BattleStruct // 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. u8 attackerBeforeBounce:2; u8 beatUpSlot:3; + bool8 hitSwitchTargetFailed:1; u8 targetsDone[MAX_BATTLERS_COUNT]; // Each battler as a bit. u16 overwrittenAbilities[MAX_BATTLERS_COUNT]; // abilities overwritten during battle (keep separate from battle history in case of switching) bool8 allowedToChangeFormInWeather[PARTY_SIZE][2]; // For each party member and side, used by Ice Face. diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 9f18455a84..0b71c22db9 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -258,6 +258,7 @@ #define VARIOUS_JUMP_IF_NO_VALID_TARGETS 166 #define VARIOUS_JUMP_IF_EMERGENCY_EXITED 167 #define VARIOUS_STORE_HEALING_WISH 168 +#define VARIOUS_HIT_SWITCH_TARGET_FAILED 169 // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 0 diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index fdd3d4f07b..5667beb4ac 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -5968,7 +5968,7 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_RED_CARD: - if (gBattleMoves[gCurrentMove].effect != EFFECT_HIT_SWITCH_TARGET + if ((gBattleMoves[gCurrentMove].effect != EFFECT_HIT_SWITCH_TARGET || gBattleStruct->hitSwitchTargetFailed) && IsBattlerAlive(gBattlerAttacker) && !TestSheerForceFlag(gBattlerAttacker, gCurrentMove) && GetBattlerAbility(gBattlerAttacker) != ABILITY_GUARD_DOG) @@ -6199,6 +6199,7 @@ static void Cmd_moveend(void) gBattleStruct->zmove.active = FALSE; gBattleStruct->zmove.toBeUsed[gBattlerAttacker] = MOVE_NONE; gBattleStruct->zmove.effect = EFFECT_HIT; + gBattleStruct->hitSwitchTargetFailed = FALSE; gBattleScripting.moveendState++; break; case MOVEEND_COUNT: @@ -11067,6 +11068,13 @@ static void Cmd_various(void) gBattleStruct->storedHealingWish |= gBitTable[gActiveBattler]; break; } + case VARIOUS_HIT_SWITCH_TARGET_FAILED: + { + VARIOUS_ARGS(); + gBattleStruct->hitSwitchTargetFailed = TRUE; + gBattlescriptCurrInstr = cmd->nextInstr; + return; + } } // End of switch (cmd->id) gBattlescriptCurrInstr = cmd->nextInstr; diff --git a/test/hold_effect_red_card.c b/test/hold_effect_red_card.c new file mode 100644 index 0000000000..c32c489d5a --- /dev/null +++ b/test/hold_effect_red_card.c @@ -0,0 +1,378 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gItems[ITEM_RED_CARD].holdEffect == HOLD_EFFECT_RED_CARD); +} + +SINGLE_BATTLE_TEST("Red Card switches the attacker with a random non-fainted replacement") +{ + PASSES_RANDOMLY(1, 2); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + MESSAGE("Foe Bulbasaur was dragged out!"); + } +} + +DOUBLE_BATTLE_TEST("Red Card switches the target with a random non-battler, non-fainted replacement") +{ + PASSES_RANDOMLY(1, 2); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + MESSAGE("Foe Bulbasaur was dragged out!"); + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if holder faints") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if target is behind a Substitute") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card activates after the last hit of a multi-hit move") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_DOUBLE_KICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_KICK, opponent); + HP_BAR(player); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if no replacements") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if replacements fainted") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { HP(0); } + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if knocked off") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if stolen by a move") +{ + u32 item; + bool32 activate; + PARAMETRIZE { item = ITEM_NONE; activate = FALSE; } + PARAMETRIZE { item = ITEM_POTION; activate = TRUE; } + ASSUME(gBattleMoves[MOVE_THIEF].effect == EFFECT_THIEF); + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET) { Item(item); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_THIEF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THIEF, opponent); + if (activate) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if stolen by Magician") +{ + u32 item; + bool32 activate; + PARAMETRIZE { item = ITEM_NONE; activate = FALSE; } + PARAMETRIZE { item = ITEM_POTION; activate = TRUE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_FENNEKIN) { Ability(ABILITY_MAGICIAN); Item(item); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + if (activate) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Fennekin!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Fennekin!"); + } + } + } +} + +DOUBLE_BATTLE_TEST("Red Card activates for only the fastest target") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(3); Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT) { Speed(2); Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WYNAUT) { Speed(4); } + OPPONENT(SPECIES_UNOWN) { Speed(1); } + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_ROCK_SLIDE); + MOVE(opponentRight, MOVE_TACKLE, target: playerRight); + } + } SCENE { + // Fastest target's Red Card activates. + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_SLIDE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + MESSAGE("Foe Unown was dragged out!"); + + // Slower target's Red Card still able to activate on other battler. + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + MESSAGE("Wynaut held up its Red Card against Foe Wynaut!"); + MESSAGE("Foe Wobbuffet was dragged out!"); + } +} + +DOUBLE_BATTLE_TEST("Red Card activates but fails if the attacker is rooted") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_UNOWN); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_INGRAIN); } + TURN { + MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); + MOVE(opponentRight, MOVE_TACKLE, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + MESSAGE("Foe Wobbuffet anchored itself with its roots!"); + + // Red Card already consumed so cannot activate. + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentRight); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + MESSAGE("Wynaut held up its Red Card against Foe Wynaut!"); + } + } +} + +DOUBLE_BATTLE_TEST("Red Card activates but fails if the attacker has Suction Cups") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_OCTILLERY) { Ability(ABILITY_SUCTION_CUPS); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_UNOWN); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); + MOVE(opponentRight, MOVE_TACKLE, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against Foe Octillery!"); + MESSAGE("Foe Octillery anchors itself with Suction Cups!"); + + // Red Card already consumed so cannot activate. + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentRight); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + MESSAGE("Wynaut held up its Red Card against Foe Wynaut!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if switched by Dragon Tail") +{ + bool32 hasWynaut, activate; + PARAMETRIZE { hasWynaut = TRUE; activate = FALSE; } + PARAMETRIZE { hasWynaut = FALSE; activate = TRUE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + if (hasWynaut) PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, opponent); + if (activate) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Red Card activates and overrides U-turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_U_TURN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Wobbuffet!"); + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if attacker's Sheer Force applied") +{ + u32 move; + bool32 activate; + PARAMETRIZE { move = MOVE_TACKLE; activate = TRUE; } + PARAMETRIZE { move = MOVE_STOMP; activate = FALSE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_TAUROS) { Ability(ABILITY_SHEER_FORCE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (activate) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Tauros!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against Foe Tauros!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Red Card activates before Emergency Exit") +{ + GIVEN { + PLAYER(SPECIES_GOLISOPOD) { MaxHP(100); HP(51); Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WIMPOD); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Golisopod held up its Red Card against Foe Wobbuffet!"); + ABILITY_POPUP(player, ABILITY_EMERGENCY_EXIT); + MESSAGE("Go! Wimpod!"); + } +} + +// SINGLE_BATTLE_TEST("Red Card activates but fails if the attacker has Dynamaxed")