diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 4fae034bfe6b..fe278d14f6a7 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -8678,6 +8678,13 @@ BattleScript_CostarActivates:: waitmessage B_WAIT_TIME_LONG end3 +BattleScript_ZeroToHeroActivates:: + pause B_WAIT_TIME_SHORT + call BattleScript_AbilityPopUp + printstring STRINGID_ZEROTOHEROTRANSFORMATION + waitmessage B_WAIT_TIME_LONG + end3 + BattleScript_AttackWeakenedByStrongWinds:: pause B_WAIT_TIME_SHORT printstring STRINGID_ATTACKWEAKENEDBSTRONGWINDS diff --git a/include/battle.h b/include/battle.h index 04db26bf4a39..350a313d939b 100644 --- a/include/battle.h +++ b/include/battle.h @@ -700,7 +700,7 @@ struct BattleStruct bool8 effectsBeforeUsingMoveDone:1; // Mega Evo and Focus Punch/Shell Trap effects. 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. + bool8 allowedToChangeFormInWeather[PARTY_SIZE][NUM_BATTLE_SIDES]; // 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. @@ -718,6 +718,7 @@ struct BattleStruct bool8 trainerSlideBeforeFirstTurnMsgDone; u32 aiDelayTimer; // Counts number of frames AI takes to choose an action. u32 aiDelayFrames; // Number of frames it took to choose an action. + bool8 transformZeroToHero[PARTY_SIZE][NUM_BATTLE_SIDES]; }; // The palaceFlags member of struct BattleStruct contains 1 flag per move to indicate which moves the AI should consider, diff --git a/include/battle_scripts.h b/include/battle_scripts.h index bdcf1081b9d1..d2addbc4c33b 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -459,6 +459,7 @@ extern const u8 BattleScript_RuinAbilityActivates[]; extern const u8 BattleScript_CudChewActivates[]; extern const u8 BattleScript_SupremeOverlordActivates[]; extern const u8 BattleScript_CostarActivates[]; +extern const u8 BattleScript_ZeroToHeroActivates[]; extern const u8 BattleScript_ToxicDebrisActivates[]; extern const u8 BattleScript_EarthEaterActivates[]; extern const u8 BattleScript_MimicryActivates_End3[]; diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index a9a6b7025b01..69e3dd935dd3 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -685,8 +685,9 @@ #define STRINGID_TEAMSURROUNDEDBYROCKS 683 #define STRINGID_PKMNHURTBYROCKSTHROWN 684 #define STRINGID_MOVEBLOCKEDBYDYNAMAX 685 +#define STRINGID_ZEROTOHEROTRANSFORMATION 686 -#define BATTLESTRINGS_COUNT 686 +#define BATTLESTRINGS_COUNT 687 // 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_message.c b/src/battle_message.c index e6e87186b48a..f2052e691925 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -822,9 +822,11 @@ static const u8 sText_TargetIsBeingSaltCured[] = _("{B_DEF_NAME_WITH_PREFIX} is static const u8 sText_TargetIsHurtBySaltCure[] = _("{B_DEF_NAME_WITH_PREFIX} is hurt by {B_BUFF1}!"); static const u8 sText_OpportunistCopied[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} copied its\nopponent's stat changes!"); static const u8 sText_TargetCoveredInStickyCandySyrup[] = _("{B_DEF_NAME_WITH_PREFIX} got covered\nin sticky syrup!"); +static const u8 sText_ZeroToHeroTransformation[] = _("{B_ATK_NAME_WITH_PREFIX} underwent a heroic\ntransformation!"); const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = { + [STRINGID_ZEROTOHEROTRANSFORMATION - BATTLESTRINGS_TABLE_START] = sText_ZeroToHeroTransformation, [STRINGID_MOVEBLOCKEDBYDYNAMAX - BATTLESTRINGS_TABLE_START] = sText_MoveBlockedByDynamax, [STRINGID_OPPORTUNISTCOPIED - BATTLESTRINGS_TABLE_START] = sText_OpportunistCopied, [STRINGID_TARGETISHURTBYSALTCURE - BATTLESTRINGS_TABLE_START] = sText_TargetIsHurtBySaltCure, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 34b861a6d48b..d5bdbaedae1f 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -9151,6 +9151,7 @@ static void Cmd_various(void) case ABILITY_SCHOOLING: case ABILITY_COMATOSE: case ABILITY_SHIELDS_DOWN: case ABILITY_DISGUISE: case ABILITY_RKS_SYSTEM: case ABILITY_TRACE: + case ABILITY_ZERO_TO_HERO: break; default: gBattleStruct->tracedAbility[gBattlerAbility] = gBattleMons[battler].ability; // re-using the variable for trace diff --git a/src/battle_util.c b/src/battle_util.c index abd5f6ac20a9..2be621be553f 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -113,6 +113,7 @@ static const u16 sSkillSwapBannedAbilities[] = ABILITY_ICE_FACE, ABILITY_HUNGER_SWITCH, ABILITY_GULP_MISSILE, + ABILITY_ZERO_TO_HERO, }; static const u16 sRolePlayBannedAbilities[] = @@ -138,6 +139,7 @@ static const u16 sRolePlayBannedAbilities[] = ABILITY_ICE_FACE, ABILITY_HUNGER_SWITCH, ABILITY_GULP_MISSILE, + ABILITY_ZERO_TO_HERO, }; static const u16 sRolePlayBannedAttackerAbilities[] = @@ -154,6 +156,7 @@ static const u16 sRolePlayBannedAttackerAbilities[] = ABILITY_POWER_CONSTRUCT, ABILITY_ICE_FACE, ABILITY_GULP_MISSILE, + ABILITY_ZERO_TO_HERO, }; static const u16 sWorrySeedBannedAbilities[] = @@ -170,6 +173,7 @@ static const u16 sWorrySeedBannedAbilities[] = ABILITY_TRUANT, ABILITY_ICE_FACE, ABILITY_GULP_MISSILE, + ABILITY_ZERO_TO_HERO, }; static const u16 sGastroAcidBannedAbilities[] = @@ -188,6 +192,7 @@ static const u16 sGastroAcidBannedAbilities[] = ABILITY_SHIELDS_DOWN, ABILITY_STANCE_CHANGE, ABILITY_ZEN_MODE, + ABILITY_ZERO_TO_HERO, }; static const u16 sEntrainmentBannedAttackerAbilities[] = @@ -206,6 +211,7 @@ static const u16 sEntrainmentBannedAttackerAbilities[] = ABILITY_ICE_FACE, ABILITY_HUNGER_SWITCH, ABILITY_GULP_MISSILE, + ABILITY_ZERO_TO_HERO, }; static const u16 sEntrainmentTargetSimpleBeamBannedAbilities[] = @@ -221,6 +227,7 @@ static const u16 sEntrainmentTargetSimpleBeamBannedAbilities[] = ABILITY_BATTLE_BOND, ABILITY_ICE_FACE, ABILITY_GULP_MISSILE, + ABILITY_ZERO_TO_HERO, }; static u8 CalcBeatUpPower(void) @@ -994,6 +1001,7 @@ static const u8 sAbilitiesNotTraced[ABILITIES_COUNT] = [ABILITY_STANCE_CHANGE] = 1, [ABILITY_TRACE] = 1, [ABILITY_ZEN_MODE] = 1, + [ABILITY_ZERO_TO_HERO] = 1, }; static const u8 sHoldEffectToType[][2] = @@ -4695,6 +4703,17 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 effect++; } break; + case ABILITY_ZERO_TO_HERO: + if (!gSpecialStatuses[battler].switchInAbilityDone + && gBattleMons[battler].species == SPECIES_PALAFIN_HERO + && !gBattleStruct->transformZeroToHero[gBattlerPartyIndexes[battler]][GetBattlerSide(battler)]) + { + gSpecialStatuses[battler].switchInAbilityDone = TRUE; + gBattleStruct->transformZeroToHero[gBattlerPartyIndexes[battler]][GetBattlerSide(battler)] = TRUE; + BattleScriptPushCursorAndCallback(BattleScript_ZeroToHeroActivates); + effect++; + } + break; } break; case ABILITYEFFECT_ENDTURN: // 1 @@ -6078,6 +6097,7 @@ bool32 IsNeutralizingGasBannedAbility(u32 ability) case ABILITY_ICE_FACE: case ABILITY_AS_ONE_ICE_RIDER: case ABILITY_AS_ONE_SHADOW_RIDER: + case ABILITY_ZERO_TO_HERO: return TRUE; default: return FALSE; diff --git a/src/data/pokemon/form_change_table_pointers.h b/src/data/pokemon/form_change_table_pointers.h index 211078837309..c3f67ce3c882 100644 --- a/src/data/pokemon/form_change_table_pointers.h +++ b/src/data/pokemon/form_change_table_pointers.h @@ -355,6 +355,8 @@ const struct FormChange *const gFormChangeTablePointers[NUM_SPECIES] = [SPECIES_ENAMORUS_THERIAN] = sEnamorusFormChangeTable, #endif #if P_GEN_9_POKEMON == TRUE + [SPECIES_PALAFIN_ZERO] = sPalafinZeroFormChangeTable, + [SPECIES_PALAFIN_HERO] = sPalafinZeroFormChangeTable, [SPECIES_OGERPON_TEAL_MASK] = sOgerponFormChangeTable, [SPECIES_OGERPON_WELLSPRING_MASK] = sOgerponFormChangeTable, [SPECIES_OGERPON_HEARTHFLAME_MASK] = sOgerponFormChangeTable, diff --git a/src/data/pokemon/form_change_tables.h b/src/data/pokemon/form_change_tables.h index 2dcadabd3673..012e1350f233 100644 --- a/src/data/pokemon/form_change_tables.h +++ b/src/data/pokemon/form_change_tables.h @@ -800,6 +800,12 @@ static const struct FormChange sUrshifuRapidStrikeFormChangeTable[] = {FORM_CHANGE_TERMINATOR}, }; +static const struct FormChange sPalafinZeroFormChangeTable[] = +{ + {FORM_CHANGE_BATTLE_SWITCH, SPECIES_PALAFIN_HERO}, + {FORM_CHANGE_TERMINATOR}, +}; + #endif #undef WHEN_LEARNED diff --git a/test/battle/ability/zero_to_hero.c b/test/battle/ability/zero_to_hero.c new file mode 100644 index 000000000000..4b9184cecae7 --- /dev/null +++ b/test/battle/ability/zero_to_hero.c @@ -0,0 +1,140 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(P_GEN_9_POKEMON == TRUE); +} + +SINGLE_BATTLE_TEST("Zero to Hero transforms Palafin when it switches out") +{ + GIVEN { + PLAYER(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_ZERO_TO_HERO); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); } + TURN { SWITCH(player, 0); } + } SCENE { + MESSAGE("Palafin, that's enough! Come back!"); + MESSAGE("Go! Wobbuffet!"); + MESSAGE("Wobbuffet, that's enough! Come back!"); + MESSAGE("Go! Palafin!"); + ABILITY_POPUP(player, ABILITY_ZERO_TO_HERO); + MESSAGE("Palafin underwent a heroic transformation!"); + } THEN { EXPECT_EQ(player->species, SPECIES_PALAFIN_HERO); } +} + +SINGLE_BATTLE_TEST("Zero to Hero can't be surpressed by Neutralizing Gas") +{ + GIVEN { + PLAYER(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_ZERO_TO_HERO); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KOFFING) { Ability(ABILITY_NEUTRALIZING_GAS); } + } WHEN { + TURN { SWITCH(player, 1); } + TURN { SWITCH(player, 0); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_NEUTRALIZING_GAS); + ABILITY_POPUP(player, ABILITY_ZERO_TO_HERO); + MESSAGE("Palafin underwent a heroic transformation!"); + } THEN { EXPECT_EQ(player->species, SPECIES_PALAFIN_HERO); } +} + +SINGLE_BATTLE_TEST("Zero to Hero transforms both player and opponent") +{ + GIVEN { + PLAYER(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_ZERO_TO_HERO); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_ZERO_TO_HERO); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); SWITCH(opponent, 1); } + TURN { SWITCH(player, 0); SWITCH(opponent, 0); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ZERO_TO_HERO); + MESSAGE("Palafin underwent a heroic transformation!"); + ABILITY_POPUP(opponent, ABILITY_ZERO_TO_HERO); + MESSAGE("Foe Palafin underwent a heroic transformation!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_PALAFIN_HERO); + EXPECT_EQ(opponent->species, SPECIES_PALAFIN_HERO); + } +} + +SINGLE_BATTLE_TEST("Zero to Hero will activate if a switch move is used") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_FLIP_TURN].effect == EFFECT_HIT_ESCAPE); + PLAYER(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_ZERO_TO_HERO); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLIP_TURN); SEND_OUT(player, 1); } + TURN { SWITCH(player, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLIP_TURN, player); + ABILITY_POPUP(player, ABILITY_ZERO_TO_HERO); + MESSAGE("Palafin underwent a heroic transformation!"); + } THEN { EXPECT_EQ(player->species, SPECIES_PALAFIN_HERO); } +} + +SINGLE_BATTLE_TEST("Gastro Acid, Worry Seed, and Simple Beam fail if the target has the Ability Zero to Hero") +{ + u16 move; + + PARAMETRIZE { move = MOVE_GASTRO_ACID; } + PARAMETRIZE { move = MOVE_WORRY_SEED; } + PARAMETRIZE { move = MOVE_SIMPLE_BEAM; } + + GIVEN { + ASSUME(gBattleMoves[MOVE_GASTRO_ACID].effect == EFFECT_GASTRO_ACID); + ASSUME(gBattleMoves[MOVE_WORRY_SEED].effect == EFFECT_WORRY_SEED); + ASSUME(gBattleMoves[MOVE_SIMPLE_BEAM].effect == EFFECT_SIMPLE_BEAM); + PLAYER(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_ZERO_TO_HERO); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, move, player); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Role Play, Skill Swap, and Entrainment fail if either Pokémon has Zero to Hero") +{ + u16 move; + + PARAMETRIZE { move = MOVE_ROLE_PLAY; } + PARAMETRIZE { move = MOVE_SKILL_SWAP; } + PARAMETRIZE { move = MOVE_ENTRAINMENT; } + + GIVEN { + ASSUME(gBattleMoves[MOVE_ROLE_PLAY].effect == EFFECT_ROLE_PLAY); + ASSUME(gBattleMoves[MOVE_SKILL_SWAP].effect == EFFECT_SKILL_SWAP); + ASSUME(gBattleMoves[MOVE_ENTRAINMENT].effect == EFFECT_ENTRAINMENT); + PLAYER(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_ZERO_TO_HERO); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, move, player); + MESSAGE("But it failed!"); + } +} + +// Write Trace test and move this one to that file (including every other ability that can't be copied) +SINGLE_BATTLE_TEST("Zero to Hero cannot be copied by Trace") +{ + GIVEN { + PLAYER(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_ZERO_TO_HERO); } + OPPONENT(SPECIES_RALTS) { Ability(ABILITY_TRACE); } + } WHEN { + TURN {} + } SCENE { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_TRACE); + MESSAGE("Foe Ralts Traced Palafin's Zero to Hero!"); + } + } +}