diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 866831c46..c875729b8 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1456,6 +1456,11 @@ .4byte \jumpInstr .endm + .macro tryreflecttype failInstr:req + callnative BS_TryReflectType + .4byte \failInstr + .endm + @ various command changed to more readable macros .macro cancelmultiturnmoves battler:req various \battler, VARIOUS_CANCEL_MULTI_TURN_MOVES @@ -1686,11 +1691,6 @@ .4byte \failInstr .endm - .macro tryreflecttype failInstr:req - various BS_ATTACKER, VARIOUS_TRY_REFLECT_TYPE - .4byte \failInstr - .endm - .macro trysoak failInstr:req various BS_ATTACKER, VARIOUS_TRY_SOAK .4byte \failInstr diff --git a/include/battle.h b/include/battle.h index 037259fc5..95886b7e1 100644 --- a/include/battle.h +++ b/include/battle.h @@ -724,12 +724,16 @@ STATIC_ASSERT(sizeof(((struct BattleStruct *)0)->palaceFlags) * 8 >= MAX_BATTLER #define BATTLER_DAMAGED(battlerId) ((gSpecialStatuses[battlerId].physicalDmg != 0 || gSpecialStatuses[battlerId].specialDmg != 0)) #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 IS_BATTLER_TYPELESS(battlerId)(GetBattlerType(battlerId, 0) == TYPE_MYSTERY && GetBattlerType(battlerId, 1) == TYPE_MYSTERY && GetBattlerType(battlerId, 2) == TYPE_MYSTERY) + #define SET_BATTLER_TYPE(battlerId, type) \ { \ gBattleMons[battlerId].type1 = type; \ gBattleMons[battlerId].type2 = type; \ gBattleMons[battlerId].type3 = TYPE_MYSTERY; \ } + #define RESTORE_BATTLER_TYPE(battlerId) \ { \ gBattleMons[battlerId].type1 = gSpeciesInfo[gBattleMons[battlerId].species].types[0]; \ diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index e9b7ba941..c886178ec 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -9409,37 +9409,6 @@ static void Cmd_various(void) } return; } - case VARIOUS_TRY_REFLECT_TYPE: - { - VARIOUS_ARGS(const u8 *failInstr); - if (gBattleMons[gBattlerTarget].species == SPECIES_ARCEUS || gBattleMons[gBattlerTarget].species == SPECIES_SILVALLY) - { - gBattlescriptCurrInstr = cmd->failInstr; - } - else if (GetBattlerType(gBattlerTarget, 0) == TYPE_MYSTERY && GetBattlerType(gBattlerTarget, 1) != TYPE_MYSTERY) - { - gBattleMons[gBattlerAttacker].type1 = GetBattlerType(gBattlerTarget, 1); - gBattleMons[gBattlerAttacker].type2 = GetBattlerType(gBattlerTarget, 1); - gBattlescriptCurrInstr = cmd->nextInstr; - } - else if (GetBattlerType(gBattlerTarget, 0) != TYPE_MYSTERY && GetBattlerType(gBattlerTarget, 1) == TYPE_MYSTERY) - { - gBattleMons[gBattlerAttacker].type1 = GetBattlerType(gBattlerTarget, 0); - gBattleMons[gBattlerAttacker].type2 = GetBattlerType(gBattlerTarget, 0); - gBattlescriptCurrInstr = cmd->nextInstr; - } - else if (GetBattlerType(gBattlerTarget, 0) == TYPE_MYSTERY && GetBattlerType(gBattlerTarget, 1) == TYPE_MYSTERY) - { - gBattlescriptCurrInstr = cmd->failInstr; - } - else - { - gBattleMons[gBattlerAttacker].type1 = GetBattlerType(gBattlerTarget, 0); - gBattleMons[gBattlerAttacker].type2 = GetBattlerType(gBattlerTarget, 1); - gBattlescriptCurrInstr = cmd->nextInstr; - } - return; - } case VARIOUS_TRY_SOAK: { VARIOUS_ARGS(const u8 *failInstr); @@ -16331,3 +16300,49 @@ void BS_JumpIfTerrainAffected(void) else gBattlescriptCurrInstr = cmd->nextInstr; } + +void BS_TryReflectType(void) +{ + NATIVE_ARGS(const u8 *failInstr); + u16 targetBaseSpecies = GET_BASE_SPECIES_ID(gBattleMons[gBattlerTarget].species); + u8 targetType1 = GetBattlerType(gBattlerTarget, 0); + u8 targetType2 = GetBattlerType(gBattlerTarget, 1); + u8 targetType3 = GetBattlerType(gBattlerTarget, 2); + + if (targetBaseSpecies == SPECIES_ARCEUS || targetBaseSpecies == SPECIES_SILVALLY) + { + gBattlescriptCurrInstr = cmd->failInstr; + } + else if (IS_BATTLER_TYPELESS(gBattlerTarget)) + { + gBattlescriptCurrInstr = cmd->failInstr; + } + else if (targetType1 == TYPE_MYSTERY && targetType2 == TYPE_MYSTERY && targetType3 != TYPE_MYSTERY) + { + gBattleMons[gBattlerAttacker].type1 = TYPE_NORMAL; + gBattleMons[gBattlerAttacker].type2 = TYPE_NORMAL; + gBattleMons[gBattlerAttacker].type3 = targetType3; + gBattlescriptCurrInstr = cmd->nextInstr; + } + else if (targetType1 == TYPE_MYSTERY && targetType2 != TYPE_MYSTERY) + { + gBattleMons[gBattlerAttacker].type1 = targetType2; + gBattleMons[gBattlerAttacker].type2 = targetType2; + gBattleMons[gBattlerAttacker].type3 = targetType3; + gBattlescriptCurrInstr = cmd->nextInstr; + } + else if (targetType1 != TYPE_MYSTERY && targetType2 == TYPE_MYSTERY) + { + gBattleMons[gBattlerAttacker].type1 = targetType1; + gBattleMons[gBattlerAttacker].type2 = targetType1; + gBattleMons[gBattlerAttacker].type3 = targetType3; + gBattlescriptCurrInstr = cmd->nextInstr; + } + else + { + gBattleMons[gBattlerAttacker].type1 = targetType1; + gBattleMons[gBattlerAttacker].type2 = targetType2; + gBattleMons[gBattlerAttacker].type3 = targetType3; + gBattlescriptCurrInstr = cmd->nextInstr; + } +} diff --git a/test/battle/move_effect/reflect_type.c b/test/battle/move_effect/reflect_type.c new file mode 100644 index 000000000..e87351603 --- /dev/null +++ b/test/battle/move_effect/reflect_type.c @@ -0,0 +1,186 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Reflect Type fails if the user is Terastallized"); +TO_DO_BATTLE_TEST("Reflect Type succeeds against a Terastallized target and copies its Tera type"); + +SINGLE_BATTLE_TEST("Reflect Type does not affect any of Arceus' forms") +{ + u32 j; + static const u16 sArceusFormSpeciesIdTable[] = { + SPECIES_ARCEUS, + SPECIES_ARCEUS_FIGHTING, + SPECIES_ARCEUS_FLYING, + SPECIES_ARCEUS_POISON, + SPECIES_ARCEUS_GROUND, + SPECIES_ARCEUS_ROCK, + SPECIES_ARCEUS_BUG, + SPECIES_ARCEUS_GHOST, + SPECIES_ARCEUS_STEEL, + SPECIES_ARCEUS_FIRE, + SPECIES_ARCEUS_WATER, + SPECIES_ARCEUS_GRASS, + SPECIES_ARCEUS_ELECTRIC, + SPECIES_ARCEUS_PSYCHIC, + SPECIES_ARCEUS_ICE, + SPECIES_ARCEUS_DRAGON, + SPECIES_ARCEUS_DARK, + SPECIES_ARCEUS_FAIRY, + }; + u16 species = SPECIES_NONE; + + for (j = 0; j < ARRAY_COUNT(sArceusFormSpeciesIdTable); j++) + { + PARAMETRIZE { species = sArceusFormSpeciesIdTable[j]; } + } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species); + } WHEN { + TURN { MOVE(player, MOVE_REFLECT_TYPE); } + } SCENE { + MESSAGE("Wobbuffet used Reflect Type!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Reflect Type does not affect any of Silvally's forms") +{ + u32 j; + static const u16 sSilvallyFormSpeciesIdTable[] = { + SPECIES_SILVALLY, + SPECIES_SILVALLY_FIGHTING, + SPECIES_SILVALLY_FLYING, + SPECIES_SILVALLY_POISON, + SPECIES_SILVALLY_GROUND, + SPECIES_SILVALLY_ROCK, + SPECIES_SILVALLY_BUG, + SPECIES_SILVALLY_GHOST, + SPECIES_SILVALLY_STEEL, + SPECIES_SILVALLY_FIRE, + SPECIES_SILVALLY_WATER, + SPECIES_SILVALLY_GRASS, + SPECIES_SILVALLY_ELECTRIC, + SPECIES_SILVALLY_PSYCHIC, + SPECIES_SILVALLY_ICE, + SPECIES_SILVALLY_DRAGON, + SPECIES_SILVALLY_DARK, + SPECIES_SILVALLY_FAIRY, + }; + u16 species = SPECIES_NONE; + + for (j = 0; j < ARRAY_COUNT(sSilvallyFormSpeciesIdTable); j++) + { + PARAMETRIZE { species = sSilvallyFormSpeciesIdTable[j]; } + } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species); + } WHEN { + TURN { MOVE(player, MOVE_REFLECT_TYPE); } + } SCENE { + MESSAGE("Wobbuffet used Reflect Type!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Reflect Type does not affect Pokémon with no types") +{ + ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[0] == TYPE_FIRE); + ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[1] == TYPE_FIRE); + ASSUME(gSpeciesInfo[SPECIES_POLIWRATH].types[0] == TYPE_WATER); + ASSUME(gSpeciesInfo[SPECIES_POLIWRATH].types[1] == TYPE_FIGHTING); + GIVEN { + PLAYER(SPECIES_ARCANINE); + OPPONENT(SPECIES_POLIWRATH); + } WHEN { + TURN { MOVE(player, MOVE_BURN_UP); MOVE(opponent, MOVE_REFLECT_TYPE); } + } SCENE { + MESSAGE("Arcanine used Burn Up!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BURN_UP, player); + HP_BAR(opponent); + MESSAGE("Arcanine burned itself out!"); + MESSAGE("Foe Poliwrath used Reflect Type!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Reflect Type copies a target's dual types") +{ + ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[0] == TYPE_FIRE); + ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[1] == TYPE_FIRE); + ASSUME(gSpeciesInfo[SPECIES_POLIWRATH].types[0] == TYPE_WATER); + ASSUME(gSpeciesInfo[SPECIES_POLIWRATH].types[1] == TYPE_FIGHTING); + GIVEN { + PLAYER(SPECIES_ARCANINE); + OPPONENT(SPECIES_POLIWRATH); + } WHEN { + TURN { MOVE(player, MOVE_REFLECT_TYPE); } + } SCENE { + MESSAGE("Arcanine used Reflect Type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT_TYPE, player); + MESSAGE("Arcanine's type changed to match the Foe Poliwrath's!"); + } THEN { + EXPECT_EQ(player->type1, TYPE_WATER); + EXPECT_EQ(player->type2, TYPE_FIGHTING); + EXPECT_EQ(player->type3, TYPE_MYSTERY); + } +} + +SINGLE_BATTLE_TEST("Reflect Type copies a target's pure type") +{ + ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[0] == TYPE_FIRE); + ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[1] == TYPE_FIRE); + ASSUME(gSpeciesInfo[SPECIES_SUDOWOODO].types[0] == TYPE_ROCK); + ASSUME(gSpeciesInfo[SPECIES_SUDOWOODO].types[1] == TYPE_ROCK); + GIVEN { + PLAYER(SPECIES_ARCANINE); + OPPONENT(SPECIES_SUDOWOODO); + } WHEN { + TURN { MOVE(player, MOVE_REFLECT_TYPE); } + } SCENE { + MESSAGE("Arcanine used Reflect Type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT_TYPE, player); + MESSAGE("Arcanine's type changed to match the Foe Sudowoodo's!"); + } THEN { + EXPECT_EQ(player->type1, TYPE_ROCK); + EXPECT_EQ(player->type2, TYPE_ROCK); + EXPECT_EQ(player->type3, TYPE_MYSTERY); + } +} + +SINGLE_BATTLE_TEST("Reflect Type defaults to Normal type for the user's type1 and type2 if the target only has a 3rd type") +{ + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] == TYPE_PSYCHIC); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] == TYPE_PSYCHIC); + ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[0] == TYPE_FIRE); + ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[1] == TYPE_FIRE); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ARCANINE); + } WHEN { + TURN { MOVE(opponent, MOVE_BURN_UP); } + TURN { MOVE(player, MOVE_FORESTS_CURSE); } + TURN { MOVE(player, MOVE_REFLECT_TYPE); } + } SCENE { + // Turn 1 + MESSAGE("Foe Arcanine used Burn Up!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BURN_UP, opponent); + HP_BAR(player); + MESSAGE("Foe Arcanine burned itself out!"); + // Turn 2 + MESSAGE("Wobbuffet used Forest'sCurs!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FORESTS_CURSE, player); + MESSAGE("Grass type was added to Foe Arcanine!"); + // Turn 3 + MESSAGE("Wobbuffet used Reflect Type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT_TYPE, player); + MESSAGE("Wobbuffet's type changed to match the Foe Arcanine's!"); + } THEN { + EXPECT_EQ(player->type1, TYPE_NORMAL); + EXPECT_EQ(player->type2, TYPE_NORMAL); + EXPECT_EQ(player->type3, TYPE_GRASS); + } +}