diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index a07c3cc56d..d28afa4877 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -370,6 +370,27 @@ gBattleScriptsForMoveEffects:: .4byte BattleScript_EffectHit @ EFFECT_FICKLE_BEAM .4byte BattleScript_EffectHit @ EFFECT_BLIZZARD .4byte BattleScript_EffectHit @ EFFECT_RAIN_ALWAYS_HIT + .4byte BattleScript_EffectShedTail @ EFFECT_SHED_TAIL + +BattleScript_EffectShedTail: + attackcanceler + attackstring + ppreduce + waitstate + jumpifstatus2 BS_ATTACKER, STATUS2_SUBSTITUTE, BattleScript_AlreadyHasSubstitute + jumpifbattletype BATTLE_TYPE_ARENA, BattleScript_ButItFailed + jumpifcantswitch SWITCH_IGNORE_ESCAPE_PREVENTION | BS_ATTACKER, BattleScript_ButItFailed + setsubstitute + jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_SUBSTITUTE_FAILED, BattleScript_SubstituteString + attackanimation + waitanimation + healthbarupdate BS_ATTACKER + datahpupdate BS_ATTACKER + printstring STRINGID_SHEDITSTAIL + waitmessage B_WAIT_TIME_LONG + moveendto MOVEEND_ATTACKER_VISIBLE + moveendfrom MOVEEND_TARGET_VISIBLE + goto BattleScript_MoveSwitchOpenPartyScreen BattleScript_EffectPsychicNoise:: printstring STRINGID_PKMNPREVENTEDFROMHEALING @@ -505,6 +526,7 @@ BattleScript_MoveSwitch: jumpifcantswitch SWITCH_IGNORE_ESCAPE_PREVENTION | BS_ATTACKER, BattleScript_MoveSwitchEnd printstring STRINGID_PKMNWENTBACK waitmessage B_WAIT_TIME_SHORT +BattleScript_MoveSwitchOpenPartyScreen: openpartyscreen BS_ATTACKER, BattleScript_MoveSwitchEnd switchoutabilities BS_ATTACKER waitstate @@ -4179,15 +4201,13 @@ BattleScript_EffectSubstitute:: waitstate jumpifstatus2 BS_ATTACKER, STATUS2_SUBSTITUTE, BattleScript_AlreadyHasSubstitute setsubstitute - jumpifbyte CMP_NOT_EQUAL, cMULTISTRING_CHOOSER, B_MSG_SUBSTITUTE_FAILED, BattleScript_SubstituteAnim - pause B_WAIT_TIME_SHORT - goto BattleScript_SubstituteString -BattleScript_SubstituteAnim:: + jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_SUBSTITUTE_FAILED, BattleScript_SubstituteString attackanimation waitanimation healthbarupdate BS_ATTACKER datahpupdate BS_ATTACKER BattleScript_SubstituteString:: + pause B_WAIT_TIME_SHORT printfromtable gSubstituteUsedStringIds waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index 4d51956293..8b427bb118 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -350,6 +350,7 @@ enum { EFFECT_FICKLE_BEAM, EFFECT_BLIZZARD, EFFECT_RAIN_ALWAYS_HIT, // Unlike EFFECT_THUNDER, it doesn't get its accuracy reduced under sun. + EFFECT_SHED_TAIL, NUM_BATTLE_MOVE_EFFECTS, }; diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index de6117e822..07a4af1854 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -700,8 +700,9 @@ #define STRINGID_ELECTROSHOCKCHARGING 698 #define STRINGID_ITEMWASUSEDUP 699 #define STRINGID_ATTACKERLOSTITSTYPE 700 +#define STRINGID_SHEDITSTAIL 701 -#define BATTLESTRINGS_COUNT 701 +#define BATTLESTRINGS_COUNT 702 // This is the string id that gBattleStringsTable starts with. // String ids before this (e.g. STRINGID_INTROMSG) are not in the table, diff --git a/src/battle_gfx_sfx_util.c b/src/battle_gfx_sfx_util.c index 4370d680b9..c14b364e77 100644 --- a/src/battle_gfx_sfx_util.c +++ b/src/battle_gfx_sfx_util.c @@ -25,6 +25,8 @@ #include "constants/songs.h" #include "constants/rgb.h" #include "constants/battle_palace.h" +#include "constants/battle_move_effects.h" + extern const u8 gBattlePalaceNatureToMoveTarget[]; extern const struct CompressedSpriteSheet gSpriteSheet_EnemyShadow; @@ -1000,7 +1002,7 @@ void LoadBattleMonGfxAndAnimate(u8 battler, bool8 loadMonSprite, u8 spriteId) void TrySetBehindSubstituteSpriteBit(u8 battler, u16 move) { - if (move == MOVE_SUBSTITUTE) + if (gBattleMoves[move].effect == EFFECT_SUBSTITUTE || gBattleMoves[move].effect == EFFECT_SHED_TAIL) gBattleSpritesDataPtr->battlerData[battler].behindSubstitute = 1; } diff --git a/src/battle_main.c b/src/battle_main.c index b65ad52293..d36f8ff386 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3156,6 +3156,11 @@ void SwitchInClearSetData(u32 battler) gDisableStructs[battler].battlerPreventingEscape = disableStructCopy.battlerPreventingEscape; gDisableStructs[battler].embargoTimer = disableStructCopy.embargoTimer; } + else if (gBattleMoves[gCurrentMove].effect == EFFECT_SHED_TAIL) + { + gBattleMons[battler].status2 |= STATUS2_SUBSTITUTE; + gDisableStructs[battler].substituteHP = disableStructCopy.substituteHP; + } gMoveResultFlags = 0; gDisableStructs[battler].isFirstTurn = 2; diff --git a/src/battle_message.c b/src/battle_message.c index 5279aa5337..f2826c2b96 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -837,9 +837,11 @@ static const u8 sText_HospitalityRestoration[] = _("The {B_ATK_PARTNER_NAME} dra static const u8 sText_ElectroShockCharging[] = _("{B_ATK_NAME_WITH_PREFIX} absorbed\nelectricity!"); static const u8 sText_ItemWasUsedUp[] = _("The {B_LAST_ITEM}\nwas used up..."); static const u8 sText_AttackerLostItsType[] = _("{B_ATK_NAME_WITH_PREFIX} lost\nits {B_BUFF1} type!"); +static const u8 sText_ShedItsTail[] = _("{B_ATK_NAME_WITH_PREFIX} shed its tail\nto create a decoy!"); const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = { + [STRINGID_SHEDITSTAIL - BATTLESTRINGS_TABLE_START] = sText_ShedItsTail, [STRINGID_ELECTROSHOCKCHARGING - BATTLESTRINGS_TABLE_START] = sText_ElectroShockCharging, [STRINGID_HOSPITALITYRESTORATION - BATTLESTRINGS_TABLE_START] = sText_HospitalityRestoration, [STRINGID_THESWAMPDISAPPEARED - BATTLESTRINGS_TABLE_START] = sText_TheSwampDisappeared, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 972ec88ced..17dfce5408 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -12391,8 +12391,10 @@ static void Cmd_setsubstitute(void) { CMD_ARGS(); - u32 hp = GetNonDynamaxMaxHP(gBattlerAttacker) / 4; - if (GetNonDynamaxMaxHP(gBattlerAttacker) / 4 == 0) + u32 factor = gBattleMoves[gCurrentMove].effect == EFFECT_SHED_TAIL ? 2 : 4; + u32 hp = GetNonDynamaxMaxHP(gBattlerAttacker) / factor; + + if (GetNonDynamaxMaxHP(gBattlerAttacker) / factor == 0) hp = 1; if (gBattleMons[gBattlerAttacker].hp <= hp) @@ -12402,7 +12404,7 @@ static void Cmd_setsubstitute(void) } else { - gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 4; // one bit value will only work for Pokémon which max hp can go to 1020(which is more than possible in games) + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / factor; // one bit value will only work for Pokémon which max hp can go to 1020(which is more than possible in games) if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; diff --git a/src/data/battle_moves.h b/src/data/battle_moves.h index e4d37559b3..0f15dd545d 100644 --- a/src/data/battle_moves.h +++ b/src/data/battle_moves.h @@ -12897,7 +12897,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = [MOVE_SHED_TAIL] = { - .effect = EFFECT_PLACEHOLDER, // EFFECT_SHED_TAIL + .effect = EFFECT_SHED_TAIL, .power = 0, .type = TYPE_NORMAL, .accuracy = 0, diff --git a/test/battle/move_effect/shed_tail.c b/test/battle/move_effect/shed_tail.c new file mode 100644 index 0000000000..258d52b3da --- /dev/null +++ b/test/battle/move_effect/shed_tail.c @@ -0,0 +1,72 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_SHED_TAIL].effect == EFFECT_SHED_TAIL); +} + +SINGLE_BATTLE_TEST("Shed Tail creates a Substitute at the cost of 1/2 users maximum HP and switches the user out") +{ + s16 maxHP = 0; + s16 costHP = 0; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SHED_TAIL); SEND_OUT(player, 1); } + } SCENE { + maxHP = GetMonData(&gPlayerParty[0], MON_DATA_HP); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHED_TAIL, player); + HP_BAR(player, captureDamage: &costHP); + MESSAGE("Wobbuffet shed its tail to create a decoy!"); + MESSAGE("Go! Wynaut!"); + }THEN { + EXPECT_EQ(maxHP / 2, costHP); + } +} + +SINGLE_BATTLE_TEST("Shed Tail fails if the user doesn't have enough HP") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SHED_TAIL); } + } SCENE { + MESSAGE("It was too weak to make a SUBSTITUTE!"); + } +} + +SINGLE_BATTLE_TEST("Shed Tail's HP cost can trigger a berry before the user switches out") +{ + GIVEN { + ASSUME(gItems[ITEM_SITRUS_BERRY].battleUsage == EFFECT_ITEM_RESTORE_HP); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SHED_TAIL); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHED_TAIL, player); + MESSAGE("Wobbuffet's Sitrus Berry restored health!"); + MESSAGE("Go! Wynaut!"); + } +} + +SINGLE_BATTLE_TEST("Shed Tail fails if there are no usable pokemon left") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) + PLAYER(SPECIES_WYNAUT) { HP(0); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SHED_TAIL); } + } SCENE { + MESSAGE("Wobbuffet used Shed Tail!"); + MESSAGE("But it failed!"); + } +} diff --git a/test/battle/move_effect/substitute.c b/test/battle/move_effect/substitute.c new file mode 100644 index 0000000000..ae2d4848b4 --- /dev/null +++ b/test/battle/move_effect/substitute.c @@ -0,0 +1,56 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_SUBSTITUTE].effect == EFFECT_SUBSTITUTE); +} + +SINGLE_BATTLE_TEST("Substitute creates a Substitute at the cost of 1/4 users maximum HP") +{ + s16 maxHP = 0; + s16 costHP = 0; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); } + } SCENE { + maxHP = GetMonData(&gPlayerParty[0], MON_DATA_HP); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + HP_BAR(player, captureDamage: &costHP); + MESSAGE("Wobbuffet made a SUBSTITUTE!"); + }THEN { + EXPECT_EQ(maxHP / 4, costHP); + } +} + +SINGLE_BATTLE_TEST("Substitute fails if the user doesn't have enough HP") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); } + } SCENE { + MESSAGE("It was too weak to make a SUBSTITUTE!"); + } +} + +SINGLE_BATTLE_TEST("Substitute's HP cost can trigger a berry") +{ + GIVEN { + ASSUME(gItems[ITEM_SITRUS_BERRY].battleUsage == EFFECT_ITEM_RESTORE_HP); + PLAYER(SPECIES_WOBBUFFET) { HP(300); Item(ITEM_SITRUS_BERRY); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + MESSAGE("Wobbuffet's Sitrus Berry restored health!"); + } +}