diff --git a/include/battle.h b/include/battle.h index f24d65c94..037259fc5 100644 --- a/include/battle.h +++ b/include/battle.h @@ -723,7 +723,7 @@ STATIC_ASSERT(sizeof(((struct BattleStruct *)0)->palaceFlags) * 8 >= MAX_BATTLER #define TARGET_TURN_DAMAGED ((gSpecialStatuses[gBattlerTarget].physicalDmg != 0 || gSpecialStatuses[gBattlerTarget].specialDmg != 0)) #define BATTLER_DAMAGED(battlerId) ((gSpecialStatuses[battlerId].physicalDmg != 0 || gSpecialStatuses[battlerId].specialDmg != 0)) -#define IS_BATTLER_OF_TYPE(battlerId, type)((gBattleMons[battlerId].type1 == type || gBattleMons[battlerId].type2 == type || (gBattleMons[battlerId].type3 != TYPE_MYSTERY && gBattleMons[battlerId].type3 == type))) +#define IS_BATTLER_OF_TYPE(battlerId, type)((GetBattlerType(battlerId, 0) == type || GetBattlerType(battlerId, 1) == type || (GetBattlerType(battlerId, 2) != TYPE_MYSTERY && GetBattlerType(battlerId, 2) == type))) #define SET_BATTLER_TYPE(battlerId, type) \ { \ gBattleMons[battlerId].type1 = type; \ diff --git a/include/battle_util.h b/include/battle_util.h index 9cdd41cab..1c9ee0960 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -248,5 +248,6 @@ void RemoveConfusionStatus(u32 battler); u8 GetBattlerGender(u32 battler); bool32 AreBattlersOfOppositeGender(u32 battler1, u32 battler2); u32 CalcSecondaryEffectChance(u32 battler, u8 secondaryEffectChance); +u8 GetBattlerType(u32 battler, u8 typeIndex); #endif // GUARD_BATTLE_UTIL_H diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 4f778530d..b289b5111 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -2430,9 +2430,9 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_SOAK: if (PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove) - || (gBattleMons[battlerDef].type1 == TYPE_WATER - && gBattleMons[battlerDef].type2 == TYPE_WATER - && gBattleMons[battlerDef].type3 == TYPE_MYSTERY)) + || (GetBattlerType(battlerDef, 0) == TYPE_WATER + && GetBattlerType(battlerDef, 1) == TYPE_WATER + && GetBattlerType(battlerDef, 2) == TYPE_MYSTERY)) score -= 10; // target is already water-only break; case EFFECT_THIRD_TYPE: @@ -2582,9 +2582,9 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_SYNCHRONOISE: //Check holding ring target or is of same type if (aiData->holdEffects[battlerDef] == HOLD_EFFECT_RING_TARGET - || IS_BATTLER_OF_TYPE(battlerDef, gBattleMons[battlerAtk].type1) - || IS_BATTLER_OF_TYPE(battlerDef, gBattleMons[battlerAtk].type2) - || IS_BATTLER_OF_TYPE(battlerDef, gBattleMons[battlerAtk].type3)) + || IS_BATTLER_OF_TYPE(battlerDef, GetBattlerType(battlerAtk, 0)) + || IS_BATTLER_OF_TYPE(battlerDef, GetBattlerType(battlerAtk, 1)) + || IS_BATTLER_OF_TYPE(battlerDef, GetBattlerType(battlerAtk, 2))) break; else score -= 10; @@ -3025,9 +3025,9 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_SOAK: if (atkPartnerAbility == ABILITY_WONDER_GUARD - && (gBattleMons[battlerAtkPartner].type1 != TYPE_WATER - || gBattleMons[battlerAtkPartner].type2 != TYPE_WATER - || gBattleMons[battlerAtkPartner].type3 != TYPE_WATER)) + && (GetBattlerType(battlerAtkPartner, 0) != TYPE_WATER + || GetBattlerType(battlerAtkPartner, 1) != TYPE_WATER + || GetBattlerType(battlerAtkPartner, 2) != TYPE_WATER)) { RETURN_SCORE_PLUS(1); } diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 399231669..b8ed2428b 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2126,12 +2126,12 @@ END: // of a move that is Super Effective against a Flying-type Pokémon. if (gBattleWeather & B_WEATHER_STRONG_WINDS) { - if ((gBattleMons[gBattlerTarget].type1 == TYPE_FLYING - && GetTypeModifier(moveType, gBattleMons[gBattlerTarget].type1) >= UQ_4_12(2.0)) - || (gBattleMons[gBattlerTarget].type2 == TYPE_FLYING - && GetTypeModifier(moveType, gBattleMons[gBattlerTarget].type2) >= UQ_4_12(2.0)) - || (gBattleMons[gBattlerTarget].type3 == TYPE_FLYING - && GetTypeModifier(moveType, gBattleMons[gBattlerTarget].type3) >= UQ_4_12(2.0))) + if ((GetBattlerType(gBattlerTarget, 0) == TYPE_FLYING + && GetTypeModifier(moveType, GetBattlerType(gBattlerTarget, 0)) >= UQ_4_12(2.0)) + || (GetBattlerType(gBattlerTarget, 1) == TYPE_FLYING + && GetTypeModifier(moveType, GetBattlerType(gBattlerTarget, 1)) >= UQ_4_12(2.0)) + || (GetBattlerType(gBattlerTarget, 2) == TYPE_FLYING + && GetTypeModifier(moveType, GetBattlerType(gBattlerTarget, 2)) >= UQ_4_12(2.0))) { gBattlerAbility = gBattlerTarget; BattleScriptPushCursor(); @@ -4909,34 +4909,8 @@ static void Cmd_setroost(void) CMD_ARGS(); gBattleResources->flags->flags[gBattlerAttacker] |= RESOURCE_FLAG_ROOST; - - // Pure flying type. - if (gBattleMons[gBattlerAttacker].type1 == TYPE_FLYING && gBattleMons[gBattlerAttacker].type2 == TYPE_FLYING) - { - gBattleStruct->roostTypes[gBattlerAttacker][0] = TYPE_FLYING; - gBattleStruct->roostTypes[gBattlerAttacker][1] = TYPE_FLYING; -#if B_ROOST_PURE_FLYING >= GEN_5 - SET_BATTLER_TYPE(gBattlerAttacker, TYPE_NORMAL); -#else - SET_BATTLER_TYPE(gBattlerAttacker, TYPE_MYSTERY); -#endif - } - // Dual type with flying type. - else if (gBattleMons[gBattlerAttacker].type1 == TYPE_FLYING || gBattleMons[gBattlerAttacker].type2 == TYPE_FLYING) - { - gBattleStruct->roostTypes[gBattlerAttacker][0] = gBattleMons[gBattlerAttacker].type1; - gBattleStruct->roostTypes[gBattlerAttacker][1] = gBattleMons[gBattlerAttacker].type2; - if (gBattleMons[gBattlerAttacker].type1 == TYPE_FLYING) - gBattleMons[gBattlerAttacker].type1 = TYPE_MYSTERY; - else if (gBattleMons[gBattlerAttacker].type2 == TYPE_FLYING) - gBattleMons[gBattlerAttacker].type2 = TYPE_MYSTERY; - } - // Non-flying type. - else - { - gBattleStruct->roostTypes[gBattlerAttacker][0] = gBattleMons[gBattlerAttacker].type1; - gBattleStruct->roostTypes[gBattlerAttacker][1] = gBattleMons[gBattlerAttacker].type2; - } + gBattleStruct->roostTypes[gBattlerAttacker][0] = gBattleMons[gBattlerAttacker].type1; + gBattleStruct->roostTypes[gBattlerAttacker][1] = gBattleMons[gBattlerAttacker].type2; gBattlescriptCurrInstr = cmd->nextInstr; } @@ -9442,26 +9416,26 @@ static void Cmd_various(void) { gBattlescriptCurrInstr = cmd->failInstr; } - else if (gBattleMons[gBattlerTarget].type1 == TYPE_MYSTERY && gBattleMons[gBattlerTarget].type2 != TYPE_MYSTERY) + else if (GetBattlerType(gBattlerTarget, 0) == TYPE_MYSTERY && GetBattlerType(gBattlerTarget, 1) != TYPE_MYSTERY) { - gBattleMons[gBattlerAttacker].type1 = gBattleMons[gBattlerTarget].type2; - gBattleMons[gBattlerAttacker].type2 = gBattleMons[gBattlerTarget].type2; + gBattleMons[gBattlerAttacker].type1 = GetBattlerType(gBattlerTarget, 1); + gBattleMons[gBattlerAttacker].type2 = GetBattlerType(gBattlerTarget, 1); gBattlescriptCurrInstr = cmd->nextInstr; } - else if (gBattleMons[gBattlerTarget].type1 != TYPE_MYSTERY && gBattleMons[gBattlerTarget].type2 == TYPE_MYSTERY) + else if (GetBattlerType(gBattlerTarget, 0) != TYPE_MYSTERY && GetBattlerType(gBattlerTarget, 1) == TYPE_MYSTERY) { - gBattleMons[gBattlerAttacker].type1 = gBattleMons[gBattlerTarget].type1; - gBattleMons[gBattlerAttacker].type2 = gBattleMons[gBattlerTarget].type1; + gBattleMons[gBattlerAttacker].type1 = GetBattlerType(gBattlerTarget, 0); + gBattleMons[gBattlerAttacker].type2 = GetBattlerType(gBattlerTarget, 0); gBattlescriptCurrInstr = cmd->nextInstr; } - else if (gBattleMons[gBattlerTarget].type1 == TYPE_MYSTERY && gBattleMons[gBattlerTarget].type2 == TYPE_MYSTERY) + else if (GetBattlerType(gBattlerTarget, 0) == TYPE_MYSTERY && GetBattlerType(gBattlerTarget, 1) == TYPE_MYSTERY) { gBattlescriptCurrInstr = cmd->failInstr; } else { - gBattleMons[gBattlerAttacker].type1 = gBattleMons[gBattlerTarget].type1; - gBattleMons[gBattlerAttacker].type2 = gBattleMons[gBattlerTarget].type2; + gBattleMons[gBattlerAttacker].type1 = GetBattlerType(gBattlerTarget, 0); + gBattleMons[gBattlerAttacker].type2 = GetBattlerType(gBattlerTarget, 1); gBattlescriptCurrInstr = cmd->nextInstr; } return; @@ -9469,8 +9443,8 @@ static void Cmd_various(void) case VARIOUS_TRY_SOAK: { VARIOUS_ARGS(const u8 *failInstr); - if (gBattleMons[gBattlerTarget].type1 == gBattleMoves[gCurrentMove].type - && gBattleMons[gBattlerTarget].type2 == gBattleMoves[gCurrentMove].type) + if (GetBattlerType(gBattlerTarget, 0) == gBattleMoves[gCurrentMove].type + && GetBattlerType(gBattlerTarget, 1) == gBattleMoves[gCurrentMove].type) { gBattlescriptCurrInstr = cmd->failInstr; } diff --git a/src/battle_util.c b/src/battle_util.c index baeff398a..25e5d87f2 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3065,11 +3065,7 @@ u8 DoBattlerEndTurnEffects(void) break; case ENDTURN_ROOST: // Return flying type. if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_ROOST) - { gBattleResources->flags->flags[battler] &= ~RESOURCE_FLAG_ROOST; - gBattleMons[battler].type1 = gBattleStruct->roostTypes[battler][0]; - gBattleMons[battler].type2 = gBattleStruct->roostTypes[battler][1]; - } gBattleStruct->turnEffectsTracker++; break; case ENDTURN_ELECTRIFY: @@ -9990,12 +9986,12 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(u32 move, u32 mov { u32 illusionSpecies; - MulByTypeEffectiveness(&modifier, move, moveType, battlerDef, gBattleMons[battlerDef].type1, battlerAtk, recordAbilities); - if (gBattleMons[battlerDef].type2 != gBattleMons[battlerDef].type1) - MulByTypeEffectiveness(&modifier, move, moveType, battlerDef, gBattleMons[battlerDef].type2, battlerAtk, recordAbilities); - if (gBattleMons[battlerDef].type3 != TYPE_MYSTERY && gBattleMons[battlerDef].type3 != gBattleMons[battlerDef].type2 - && gBattleMons[battlerDef].type3 != gBattleMons[battlerDef].type1) - MulByTypeEffectiveness(&modifier, move, moveType, battlerDef, gBattleMons[battlerDef].type3, battlerAtk, recordAbilities); + MulByTypeEffectiveness(&modifier, move, moveType, battlerDef, GetBattlerType(battlerDef, 0), battlerAtk, recordAbilities); + if (GetBattlerType(battlerDef, 1) != GetBattlerType(battlerDef, 0)) + MulByTypeEffectiveness(&modifier, move, moveType, battlerDef, GetBattlerType(battlerDef, 1), battlerAtk, recordAbilities); + if (GetBattlerType(battlerDef, 2) != TYPE_MYSTERY && GetBattlerType(battlerDef, 2) != GetBattlerType(battlerDef, 1) + && GetBattlerType(battlerDef, 2) != GetBattlerType(battlerDef, 0)) + MulByTypeEffectiveness(&modifier, move, moveType, battlerDef, GetBattlerType(battlerDef, 2), battlerAtk, recordAbilities); if (recordAbilities && (illusionSpecies = GetIllusionMonSpecies(battlerDef))) TryNoticeIllusionInTypeEffectiveness(move, moveType, battlerAtk, battlerDef, modifier, illusionSpecies); @@ -10506,8 +10502,8 @@ bool32 TryBattleFormChange(u32 battler, u16 method) bool32 DoBattlersShareType(u32 battler1, u32 battler2) { s32 i; - u8 types1[3] = {gBattleMons[battler1].type1, gBattleMons[battler1].type2, gBattleMons[battler1].type3}; - u8 types2[3] = {gBattleMons[battler2].type1, gBattleMons[battler2].type2, gBattleMons[battler2].type3}; + u8 types1[3] = {GetBattlerType(battler1, 0), GetBattlerType(battler1, 1), GetBattlerType(battler1, 2)}; + u8 types2[3] = {GetBattlerType(battler2, 0), GetBattlerType(battler2, 1), GetBattlerType(battler2, 2)}; if (types1[2] == TYPE_MYSTERY) types1[2] = types1[0]; @@ -11199,3 +11195,31 @@ bool32 IsGen6ExpShareEnabled(void) return FlagGet(I_EXP_SHARE_FLAG); #endif } + + +u8 GetBattlerType(u32 battler, u8 typeIndex) +{ + u16 types[3] = {0}; + types[0] = gBattleMons[battler].type1; + types[1] = gBattleMons[battler].type2; + types[2] = gBattleMons[battler].type3; + + // Handle Roost's Flying-type suppression + if (typeIndex == 0 || typeIndex == 1) + { + if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_ROOST) + { + if (types[0] == TYPE_FLYING && types[1] == TYPE_FLYING) +#if B_ROOST_PURE_FLYING >= GEN_5 + return TYPE_NORMAL; +#else + return TYPE_MYSTERY; +#endif + else + return types[typeIndex] == TYPE_FLYING ? TYPE_MYSTERY : types[typeIndex]; + } + } + + return types[typeIndex]; +} + diff --git a/test/battle/move_effect/roost.c b/test/battle/move_effect/roost.c new file mode 100644 index 000000000..4a3f90602 --- /dev/null +++ b/test/battle/move_effect/roost.c @@ -0,0 +1,432 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_ROOST].effect == EFFECT_ROOST); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] != TYPE_FLYING); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] != TYPE_FLYING); + // One attack of each type to verify typelessness + ASSUME(gBattleMoves[MOVE_POUND].type == TYPE_NORMAL); + ASSUME(gBattleMoves[MOVE_KARATE_CHOP].type == TYPE_FIGHTING); + ASSUME(gBattleMoves[MOVE_GUST].type == TYPE_FLYING); + ASSUME(gBattleMoves[MOVE_POISON_STING].type == TYPE_POISON); + ASSUME(gBattleMoves[MOVE_EARTHQUAKE].type == TYPE_GROUND); + ASSUME(gBattleMoves[MOVE_ROCK_THROW].type == TYPE_ROCK); + ASSUME(gBattleMoves[MOVE_LEECH_LIFE].type == TYPE_BUG); + ASSUME(gBattleMoves[MOVE_LICK].type == TYPE_GHOST); + ASSUME(gBattleMoves[MOVE_STEEL_WING].type == TYPE_STEEL); + ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE); + ASSUME(gBattleMoves[MOVE_WATER_GUN].type == TYPE_WATER); + ASSUME(gBattleMoves[MOVE_VINE_WHIP].type == TYPE_GRASS); + ASSUME(gBattleMoves[MOVE_THUNDER_SHOCK].type == TYPE_ELECTRIC); + ASSUME(gBattleMoves[MOVE_CONFUSION].type == TYPE_PSYCHIC); + ASSUME(gBattleMoves[MOVE_ICE_BEAM].type == TYPE_ICE); + ASSUME(gBattleMoves[MOVE_DRAGON_BREATH].type == TYPE_DRAGON); + ASSUME(gBattleMoves[MOVE_BITE].type == TYPE_DARK); + ASSUME(gBattleMoves[MOVE_DISARMING_VOICE].type == TYPE_FAIRY); +} + +SINGLE_BATTLE_TEST("Roost fails when user is at full HP") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); } + } SCENE { + MESSAGE("Wobbuffet's HP is full!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Roost fails if the user is under the effects of Heal Block") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_HEAL_BLOCK); MOVE(player, MOVE_ROOST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEAL_BLOCK, opponent); + MESSAGE("Wobbuffet was prevented from healing!"); // Message when Heal Block is applied + MESSAGE("Wobbuffet was prevented from healing!"); // Message when trying to heal under Heal Block + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Roost recovers 50% of the user's Max HP") +{ + s16 hp; + + KNOWN_FAILING; // All healing is currently rounded down + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(99); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + HP_BAR(player, captureHP: &hp); + } THEN { + //if (B_UPDATED_MOVE_DATA >= GEN_5) + EXPECT(hp == 51); // Rounds up + //else + // EXPECT(hp == 50); // Rounds down + } +} + +SINGLE_BATTLE_TEST("Roost suppresses the user's Flying-typing this turn, then restores it at the end of the turn") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_SKARMORY].types[0] == TYPE_STEEL); + ASSUME(gSpeciesInfo[SPECIES_SKARMORY].types[1] == TYPE_FLYING); + PLAYER(SPECIES_SKARMORY) { HP(50); MaxHP(100); Ability(ABILITY_STURDY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, MOVE_EARTHQUAKE); } + TURN { MOVE(opponent, MOVE_EARTHQUAKE); } + } SCENE { + // Turn 1: EQ hits when Roosted + MESSAGE("Skarmory used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Skarmory regained health!"); + MESSAGE("Foe Wobbuffet used Earthquake!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponent); + MESSAGE("It's super effective!"); + // Turn 2: EQ has no effect because Roost expired + MESSAGE("Foe Wobbuffet used Earthquake!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponent); + MESSAGE("It doesn't affect Skarmory…"); + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Roost, if used by a Flying/Flying type, treats the user as a Normal-type (or Typeless in Gen. 4) until the end of the turn") +{ + u32 damagingMove; + PARAMETRIZE{ damagingMove = MOVE_POUND; } + PARAMETRIZE{ damagingMove = MOVE_KARATE_CHOP; } + PARAMETRIZE{ damagingMove = MOVE_GUST; } + PARAMETRIZE{ damagingMove = MOVE_POISON_STING; } + PARAMETRIZE{ damagingMove = MOVE_EARTHQUAKE; } + PARAMETRIZE{ damagingMove = MOVE_ROCK_THROW; } + PARAMETRIZE{ damagingMove = MOVE_LEECH_LIFE; } + PARAMETRIZE{ damagingMove = MOVE_LICK; } + PARAMETRIZE{ damagingMove = MOVE_STEEL_WING; } + PARAMETRIZE{ damagingMove = MOVE_EMBER; } + PARAMETRIZE{ damagingMove = MOVE_WATER_GUN; } + PARAMETRIZE{ damagingMove = MOVE_VINE_WHIP; } + PARAMETRIZE{ damagingMove = MOVE_THUNDER_SHOCK; } + PARAMETRIZE{ damagingMove = MOVE_CONFUSION; } + PARAMETRIZE{ damagingMove = MOVE_ICE_BEAM; } + PARAMETRIZE{ damagingMove = MOVE_DRAGON_BREATH; } + PARAMETRIZE{ damagingMove = MOVE_BITE; } + PARAMETRIZE{ damagingMove = MOVE_DISARMING_VOICE; } + + GIVEN { + ASSUME(P_GEN_5_POKEMON == TRUE); + ASSUME(gSpeciesInfo[SPECIES_TORNADUS].types[0] == TYPE_FLYING); + ASSUME(gSpeciesInfo[SPECIES_TORNADUS].types[1] == TYPE_FLYING); + PLAYER(SPECIES_TORNADUS) { HP(50); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, damagingMove); } + } SCENE { + MESSAGE("Tornadus used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Tornadus regained health!"); + + if (B_ROOST_PURE_FLYING >= GEN_5) // >= Gen. 5, Pokemon becomes pure Normal-type + { + if (damagingMove == MOVE_KARATE_CHOP) + { + ANIMATION(ANIM_TYPE_MOVE, damagingMove, opponent); + MESSAGE("It's super effective!"); + } + else if (damagingMove == MOVE_LICK) + { + NOT ANIMATION(ANIM_TYPE_MOVE, damagingMove, opponent); + MESSAGE("It doesn't affect Tornadus…"); + } + else + { + ANIMATION(ANIM_TYPE_MOVE, damagingMove, opponent); + NONE_OF { + MESSAGE("It's super effective!"); + MESSAGE("It's not very effective…"); + MESSAGE("It doesn't affect Tornadus…"); + } + } + } + else // <= Gen. 4, Pokemon becomes Typeless + { + // Should not see any effectiveness messages + NONE_OF { + MESSAGE("It's super effective!"); + MESSAGE("It's not very effective…"); + MESSAGE("It doesn't affect Tornadus…"); + } + } + } +} + +SINGLE_BATTLE_TEST("Roost, if used by a Mystery/Flying type, treats the user as a Mystery/Mystery type until the end of the turn") +{ + u32 damagingMove; + PARAMETRIZE{ damagingMove = MOVE_POUND; } + PARAMETRIZE{ damagingMove = MOVE_KARATE_CHOP; } + PARAMETRIZE{ damagingMove = MOVE_GUST; } + PARAMETRIZE{ damagingMove = MOVE_POISON_STING; } + PARAMETRIZE{ damagingMove = MOVE_EARTHQUAKE; } + PARAMETRIZE{ damagingMove = MOVE_ROCK_THROW; } + PARAMETRIZE{ damagingMove = MOVE_LEECH_LIFE; } + PARAMETRIZE{ damagingMove = MOVE_LICK; } + PARAMETRIZE{ damagingMove = MOVE_STEEL_WING; } + PARAMETRIZE{ damagingMove = MOVE_EMBER; } + PARAMETRIZE{ damagingMove = MOVE_WATER_GUN; } + PARAMETRIZE{ damagingMove = MOVE_VINE_WHIP; } + PARAMETRIZE{ damagingMove = MOVE_THUNDER_SHOCK; } + PARAMETRIZE{ damagingMove = MOVE_CONFUSION; } + PARAMETRIZE{ damagingMove = MOVE_ICE_BEAM; } + PARAMETRIZE{ damagingMove = MOVE_DRAGON_BREATH; } + PARAMETRIZE{ damagingMove = MOVE_BITE; } + PARAMETRIZE{ damagingMove = MOVE_DISARMING_VOICE; } + + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_MOLTRES].types[0] == TYPE_FIRE); + ASSUME(gSpeciesInfo[SPECIES_MOLTRES].types[1] == TYPE_FLYING); + PLAYER(SPECIES_MOLTRES) { HP(300); MaxHP(400); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BURN_UP); } + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, damagingMove); } + } SCENE { + // Turn 1: Use Burn Up to change from Fire/Flying to Mystery/Flying + MESSAGE("Moltres used Burn Up!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BURN_UP, player); + MESSAGE("Moltres burned itself out!"); + // Turn 2: Use Roost to now be treated as a Mystery/Mystery type + MESSAGE("Moltres used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Moltres regained health!"); + ANIMATION(ANIM_TYPE_MOVE, damagingMove, opponent); + NONE_OF { + MESSAGE("It's super effective!"); + MESSAGE("It's not very effective…"); + MESSAGE("It doesn't affect Moltres…"); + } + } +} + +// Tested in ORAS +DOUBLE_BATTLE_TEST("Roost suppresses the user's not-yet-aquired Flying-type this turn") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_KECLEON].types[0] != TYPE_FLYING); + ASSUME(gSpeciesInfo[SPECIES_KECLEON].types[1] != TYPE_FLYING); + PLAYER(SPECIES_KECLEON) { Speed(40); HP(150); Ability(ABILITY_COLOR_CHANGE); } + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_PIDGEY) { Speed(30); } + OPPONENT(SPECIES_SANDSHREW) { Speed(20); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_ROOST); + MOVE(opponentLeft, MOVE_GUST, target: playerLeft); + MOVE(opponentRight, MOVE_EARTHQUAKE, target: playerLeft); } + } SCENE { + MESSAGE("Kecleon used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, playerLeft); + MESSAGE("Kecleon regained health!"); + MESSAGE("Foe Pidgey used Gust!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUST, opponentLeft); + MESSAGE("Kecleon's Color Change made it the Flying type!"); + MESSAGE("Foe Sandshrew used Earthquake!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponentRight); + MESSAGE("Kecleon's Color Change made it the Ground type!"); + } +} + +// Tested in ORAS +SINGLE_BATTLE_TEST("Roost prevents a Flying-type user from being protected by Delta Stream") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_RAYQUAZA].types[1] == TYPE_FLYING); + PLAYER(SPECIES_RAYQUAZA) { HP(1); Ability(ABILITY_DELTA_STREAM); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, MOVE_ICE_BEAM); } + } SCENE { + MESSAGE("Rayquaza used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Rayquaza regained health!"); + MESSAGE("Foe Wobbuffet used Ice Beam!"); + NOT MESSAGE("The mysterious strong winds weakened the attack!"); + } +} + +SINGLE_BATTLE_TEST("Roost does not undo other type-changing effects at the end of the turn") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_SWELLOW].types[0] == TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_SWELLOW].types[1] == TYPE_FLYING); + PLAYER(SPECIES_SWELLOW) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, MOVE_SOAK); } + TURN { MOVE(opponent, MOVE_VINE_WHIP); } + } SCENE { + MESSAGE("Swellow used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Swellow regained health!"); + MESSAGE("Foe Wobbuffet used Soak!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SOAK, opponent); + MESSAGE("Swellow transformed into the Water type!"); + MESSAGE("Foe Wobbuffet used Vine Whip!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_VINE_WHIP, opponent); + MESSAGE("It's super effective!"); + } +} + +// https://www.smogon.com/forums/threads/sword-shield-battle-mechanics-research.3655528/page-64#post-9244179 +SINGLE_BATTLE_TEST("Roost's effect is lifted after Grassy Terrain's healing") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_SWELLOW].types[0] == TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_SWELLOW].types[1] == TYPE_FLYING); + PLAYER(SPECIES_SWELLOW) { HP(1); Ability(ABILITY_GRASSY_SURGE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); } + } SCENE { + MESSAGE("Swellow used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Swellow regained health!"); + MESSAGE("Swellow is healed by the grassy terrain!"); + HP_BAR(player); + } +} + +// Tested in USUM +SINGLE_BATTLE_TEST("Roost's suppression prevents Reflect Type from copying any Flying typing") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_SWELLOW].types[0] == TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_SWELLOW].types[1] == TYPE_FLYING); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] == TYPE_PSYCHIC); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] == TYPE_PSYCHIC); + PLAYER(SPECIES_SWELLOW) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, MOVE_REFLECT_TYPE); } + TURN { MOVE(player, MOVE_EARTHQUAKE); MOVE(opponent, MOVE_REFLECT_TYPE); } + TURN { MOVE(player, MOVE_EARTHQUAKE); } + } SCENE { + // Turn 1: Reflect Type on Roosted Normal/Flying + MESSAGE("Swellow used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Swellow regained health!"); + MESSAGE("Foe Wobbuffet used Reflect Type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT_TYPE, opponent); + MESSAGE("Foe Wobbuffet's type changed to match the Swellow's!"); + // Turn 2: EQ hits, Reflect Type on non-Roosted Normal/Flying + MESSAGE("Swellow used Earthquake!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, player); + HP_BAR(opponent); + MESSAGE("Foe Wobbuffet used Reflect Type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT_TYPE, opponent); + MESSAGE("Foe Wobbuffet's type changed to match the Swellow's!"); + // Turn 3: EQ has no effect + MESSAGE("Swellow used Earthquake!"); + MESSAGE("It doesn't affect Foe Wobbuffet…"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, player); + NOT HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Roost does not suppress the ungrounded effect of Levitate") +{ + GIVEN { + PLAYER(SPECIES_FLYGON) { HP(1); Ability(ABILITY_LEVITATE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, MOVE_EARTHQUAKE); } + } SCENE { + MESSAGE("Flygon used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Flygon regained health!"); + MESSAGE("Foe Wobbuffet used Earthquake!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponent); + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Roost does not suppress the ungrounded effect of Air Balloon") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Item(ITEM_AIR_BALLOON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, MOVE_EARTHQUAKE); } + } SCENE { + MESSAGE("Wobbuffet used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Wobbuffet regained health!"); + MESSAGE("Foe Wobbuffet used Earthquake!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponent); + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Roost does not suppress the ungrounded effect of Magnet Rise") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MAGNET_RISE); } + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, MOVE_EARTHQUAKE); } + } SCENE { + // Turn 1: Magnet Rise + MESSAGE("Wobbuffet used Magnet Rise!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGNET_RISE, player); + MESSAGE("Wobbuffet levitated on electromagnetism!"); + // Turn 2 + MESSAGE("Wobbuffet used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Wobbuffet regained health!"); + MESSAGE("Foe Wobbuffet used Earthquake!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponent); + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Roost does not suppress the ungrounded effect of Telekinesis") +{ + KNOWN_FAILING; // Telekinesis currently says the pokemon was identified + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TELEKINESIS); } + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, MOVE_EARTHQUAKE); } + } SCENE { + // Turn 1: Telekinesis + MESSAGE("Foe Wobbuffet used Telekinesis!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TELEKINESIS, opponent); + MESSAGE("Wobbuffet was hurled into the air!"); + // Turn 2 + MESSAGE("Wobbuffet used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Wobbuffet regained health!"); + MESSAGE("Foe Wobbuffet used Earthquake!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponent); + NOT HP_BAR(player); + } +} + +// Tested in ORAS +// Transform does not copy the Roost "status" either. +// Probably better as a Transform test. +TO_DO_BATTLE_TEST("Roost's suppression does not prevent others who are Transforming into the user from copying its Flying-type");