diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index d6301f8be7..3c52b961c9 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2089,6 +2089,11 @@ static inline bool32 TryTeraShellDistortTypeMatchups(u32 battlerDef) // It doesn't have any impact on gameplay and is only a visual thing which can be adjusted later. static inline bool32 TryActivateWeaknessBerry(u32 battlerDef) { + if (DoesDisguiseBlockMove(battlerDef, gCurrentMove)) + { + gSpecialStatuses[battlerDef].berryReduced = FALSE; + return FALSE; + } if (gSpecialStatuses[battlerDef].berryReduced && gBattleMons[battlerDef].item != ITEM_NONE) { gBattleScripting.battler = battlerDef; @@ -2354,6 +2359,7 @@ static void MoveDamageDataHpUpdate(u32 battler, u32 scriptBattler, const u8 *nex if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) { gBattlescriptCurrInstr = nextInstr; + return; } else if (DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove) && gDisableStructs[battler].substituteHP) { @@ -2371,18 +2377,19 @@ static void MoveDamageDataHpUpdate(u32 battler, u32 scriptBattler, const u8 *nex { gBattlescriptCurrInstr = nextInstr; BattleScriptCall(BattleScript_SubstituteFade); - return; } else { gBattlescriptCurrInstr = nextInstr; - return; } + return; } else if (DoesDisguiseBlockMove(battler, gCurrentMove)) { // TODO: Convert this to a proper FORM_CHANGE type. gBattleScripting.battler = battler; + gBattleStruct->moveDamage[battler] = 0; + gBattleStruct->moveResultFlags[battler] &= ~(MOVE_RESULT_NOT_VERY_EFFECTIVE | MOVE_RESULT_SUPER_EFFECTIVE); if (GetBattlerPartyState(battler)->changedSpecies == SPECIES_NONE) GetBattlerPartyState(battler)->changedSpecies = gBattleMons[battler].species; if (gBattleMons[battler].species == SPECIES_MIMIKYU_TOTEM_DISGUISED) @@ -2421,28 +2428,6 @@ static void MoveDamageDataHpUpdate(u32 battler, u32 scriptBattler, const u8 *nex gBattleStruct->moveDamage[battler] = gBattleMons[battler].hp; gBattleMons[battler].hp = 0; } - - // Note: While physicalDmg/specialDmg below are only distinguished between for Counter/Mirror Coat, they are - // used in combination as general damage trackers for other purposes. specialDmg is additionally used - // to help determine if a fire move should defrost the target. - if (IsBattleMovePhysical(gCurrentMove)) - { - gProtectStructs[battler].physicalDmg = gBattleStruct->moveDamage[battler]; - gSpecialStatuses[battler].physicalDmg = gBattleStruct->moveDamage[battler]; - if (scriptBattler == BS_TARGET) // What's the point of this??? It will be always target - gProtectStructs[battler].physicalBattlerId = gBattlerAttacker; - else - gProtectStructs[battler].physicalBattlerId = gBattlerTarget; - } - else // Physical move - { - gProtectStructs[battler].specialDmg = gBattleStruct->moveDamage[battler]; - gSpecialStatuses[battler].specialDmg = gBattleStruct->moveDamage[battler]; - if (scriptBattler == BS_TARGET) // What's the point of this??? It will be always target - gProtectStructs[battler].specialBattlerId = gBattlerAttacker; - else - gProtectStructs[battler].specialBattlerId = gBattlerTarget; - } gProtectStructs[battler].assuranceDoubled = TRUE; gProtectStructs[battler].revengeDoubled |= 1u << gBattlerAttacker; @@ -2453,6 +2438,21 @@ static void MoveDamageDataHpUpdate(u32 battler, u32 scriptBattler, const u8 *nex gBattlescriptCurrInstr = nextInstr; } + // Note: While physicalDmg/specialDmg below are only distinguished between for Counter/Mirror Coat, + // they are used in combination as general damage trackers for other purposes. + if (IsBattleMovePhysical(gCurrentMove)) + { + gProtectStructs[battler].physicalDmg = gBattleStruct->moveDamage[battler] + 1; + gSpecialStatuses[battler].physicalDmg = gBattleStruct->moveDamage[battler] + 1; + gProtectStructs[battler].physicalBattlerId = gBattlerAttacker; + } + else // Special move + { + gProtectStructs[battler].specialDmg = gBattleStruct->moveDamage[battler] + 1; + gSpecialStatuses[battler].specialDmg = gBattleStruct->moveDamage[battler] + 1; + gProtectStructs[battler].specialBattlerId = gBattlerAttacker; + } + if (IsBattlerTurnDamaged(gBattlerTarget) && GetMoveCategory(gCurrentMove) != DAMAGE_CATEGORY_STATUS) GetBattlerPartyState(battler)->timesGotHit++; } @@ -5918,7 +5918,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) } break; case EFFECT_RECOIL: - if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) + if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker) && gBattleStruct->moveDamage[gBattlerTarget] > 0) { enum Ability ability = GetBattlerAbility(gBattlerAttacker); if (IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_ROCK_HEAD) @@ -11346,6 +11346,13 @@ static void Cmd_unused_0xA0(void) { } +static void CalcReflectBackDamage(u32 baseDamage, u32 percentMult) +{ + s32 damage = (baseDamage - 1) * percentMult / 100; + damage = max(damage, 1); + gBattleStruct->moveDamage[gBattlerTarget] = damage; +} + static void Cmd_counterdamagecalculator(void) { CMD_ARGS(const u8 *failInstr); @@ -11362,7 +11369,7 @@ static void Cmd_counterdamagecalculator(void) else gBattlerTarget = gProtectStructs[gBattlerAttacker].physicalBattlerId; - gBattleStruct->moveDamage[gBattlerTarget] = gProtectStructs[gBattlerAttacker].physicalDmg * 2; + CalcReflectBackDamage(gProtectStructs[gBattlerAttacker].physicalDmg, 200); gBattlescriptCurrInstr = cmd->nextInstr; } else @@ -11389,7 +11396,7 @@ static void Cmd_mirrorcoatdamagecalculator(void) else gBattlerTarget = gProtectStructs[gBattlerAttacker].specialBattlerId; - gBattleStruct->moveDamage[gBattlerTarget] = gProtectStructs[gBattlerAttacker].specialDmg * 2; + CalcReflectBackDamage(gProtectStructs[gBattlerAttacker].specialDmg, 200); gBattlescriptCurrInstr = cmd->nextInstr; } else @@ -14522,7 +14529,7 @@ void BS_CalcMetalBurstDmg(void) else gBattlerTarget = gProtectStructs[gBattlerAttacker].physicalBattlerId; - gBattleStruct->moveDamage[gBattlerTarget] = gProtectStructs[gBattlerAttacker].physicalDmg * 150 / 100; + CalcReflectBackDamage(gProtectStructs[gBattlerAttacker].physicalDmg, 150); gBattlescriptCurrInstr = cmd->nextInstr; } else if (gProtectStructs[gBattlerAttacker].specialDmg @@ -14535,7 +14542,7 @@ void BS_CalcMetalBurstDmg(void) else gBattlerTarget = gProtectStructs[gBattlerAttacker].specialBattlerId; - gBattleStruct->moveDamage[gBattlerTarget] = gProtectStructs[gBattlerAttacker].specialDmg * 150 / 100; + CalcReflectBackDamage(gProtectStructs[gBattlerAttacker].specialDmg, 150); gBattlescriptCurrInstr = cmd->nextInstr; } else diff --git a/test/battle/hold_effect/resist_berry.c b/test/battle/hold_effect/resist_berry.c index 4958c67811..b13ca35665 100644 --- a/test/battle/hold_effect/resist_berry.c +++ b/test/battle/hold_effect/resist_berry.c @@ -124,3 +124,22 @@ SINGLE_BATTLE_TEST("Weakness berries do not decrease the power of Struggle", s16 EXPECT_EQ(results[0].damage, results[1].damage); } } + +SINGLE_BATTLE_TEST("Weakness berries do not activate if Disguise blocks the damage") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_BABIRI_BERRY) == HOLD_EFFECT_RESIST_BERRY); + ASSUME(GetItemHoldEffectParam(ITEM_BABIRI_BERRY) == TYPE_STEEL); + ASSUME(GetMoveType(MOVE_METAL_CLAW) == TYPE_STEEL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MIMIKYU) { Item(ITEM_BABIRI_BERRY); Ability(ABILITY_DISGUISE); } + } WHEN { + TURN { MOVE(player, MOVE_METAL_CLAW); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The Babiri Berry weakened the damage to the opposing Mimikyu!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_METAL_CLAW, player); + } +} diff --git a/test/battle/hold_effect/rocky_helmet.c b/test/battle/hold_effect/rocky_helmet.c index 3f8a4ca5c9..bc52f44b87 100644 --- a/test/battle/hold_effect/rocky_helmet.c +++ b/test/battle/hold_effect/rocky_helmet.c @@ -1,4 +1,25 @@ #include "global.h" #include "test/battle.h" +ASSUMPTIONS +{ + ASSUME(GetItemHoldEffect(ITEM_ROCKY_HELMET) == HOLD_EFFECT_ROCKY_HELMET); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + ASSUME(MoveMakesContact(MOVE_SHADOW_SNEAK)); +} + +SINGLE_BATTLE_TEST("Rocky Helmet damages attacker even if damage is blocked by Disguise") +{ + GIVEN { + PLAYER(SPECIES_MIMIKYU) { Item(ITEM_ROCKY_HELMET); Ability(ABILITY_DISGUISE); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SHADOW_SNEAK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHADOW_SNEAK, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(opponent); + } +} + TO_DO_BATTLE_TEST("TODO: Write Rocky Helmet (Hold Effect) test titles") diff --git a/test/battle/hold_effect/weakness_policy.c b/test/battle/hold_effect/weakness_policy.c index 78230ed0d5..d080e9ad65 100644 --- a/test/battle/hold_effect/weakness_policy.c +++ b/test/battle/hold_effect/weakness_policy.c @@ -1,4 +1,31 @@ #include "global.h" #include "test/battle.h" +ASSUMPTIONS +{ + ASSUME(GetItemHoldEffect(ITEM_WEAKNESS_POLICY) == HOLD_EFFECT_WEAKNESS_POLICY); +} + +SINGLE_BATTLE_TEST("Weakness Policy does not activate if Disguise blocks the damage") +{ + u32 species; + + PARAMETRIZE { species = SPECIES_MIMIKYU_BUSTED; } + PARAMETRIZE { species = SPECIES_MIMIKYU_DISGUISED; } + + GIVEN { + ASSUME(GetMoveType(MOVE_METAL_CLAW) == TYPE_STEEL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Item(ITEM_WEAKNESS_POLICY); Ability(ABILITY_DISGUISE); } + } WHEN { + TURN { MOVE(player, MOVE_METAL_CLAW); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_METAL_CLAW, player); + if (species == SPECIES_MIMIKYU_BUSTED) + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + else + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} + TO_DO_BATTLE_TEST("TODO: Write Weakness Policy (Hold Effect) test titles") diff --git a/test/battle/move_effect/absorb.c b/test/battle/move_effect/absorb.c index d497bc3f04..922d16fa0c 100644 --- a/test/battle/move_effect/absorb.c +++ b/test/battle/move_effect/absorb.c @@ -118,3 +118,17 @@ SINGLE_BATTLE_TEST("Absorb does not drain any HP if user does 0 damage") NOT MESSAGE("The opposing Wobbuffet had its energy drained!"); } } + +SINGLE_BATTLE_TEST("Absorb does not drain any HP if the move is blocked by Disguise") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_MIMIKYU) { Ability(ABILITY_DISGUISE); } + } WHEN { + TURN { MOVE(player, MOVE_ABSORB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player); + } THEN { + EXPECT_EQ(player->hp, 1); + } +} diff --git a/test/battle/move_effect/counter.c b/test/battle/move_effect/counter.c index ece0ea9a9f..7fc9fb4b9f 100644 --- a/test/battle/move_effect/counter.c +++ b/test/battle/move_effect/counter.c @@ -129,7 +129,7 @@ DOUBLE_BATTLE_TEST("Counter respects Follow me") } } -DOUBLE_BATTLE_TEST("Counter fails if mon that damaged counter user is no longer on the field") +DOUBLE_BATTLE_TEST("Counter fails if mon that damaged Counter user is no longer on the field (Gen 1-4)") { GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -149,6 +149,24 @@ DOUBLE_BATTLE_TEST("Counter fails if mon that damaged counter user is no longer } } +SINGLE_BATTLE_TEST("Counter deals 1 damage when the attack received is blocked by Disguise") +{ + s16 counterDmg; + GIVEN { + ASSUME(GetMoveCategory(MOVE_SHADOW_SNEAK) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_MIMIKYU) { Ability(ABILITY_DISGUISE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SHADOW_SNEAK); MOVE(player, MOVE_COUNTER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHADOW_SNEAK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COUNTER, player); + HP_BAR(opponent, captureDamage: &counterDmg); + } THEN { + EXPECT_EQ(counterDmg, 1); + } +} + // Gen 1 TO_DO_BATTLE_TEST("Counter can only counter Normal and Fighting-type moves (Gen 1)"); TO_DO_BATTLE_TEST("Counter can hit ghost-type Pokémon (Gen 1)"); diff --git a/test/battle/move_effect/mirror_coat.c b/test/battle/move_effect/mirror_coat.c index 2a3d6ef250..6de1c03c29 100644 --- a/test/battle/move_effect/mirror_coat.c +++ b/test/battle/move_effect/mirror_coat.c @@ -1,4 +1,131 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Mirror Coat (Move Effect) test titles") +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_MIRROR_COAT) == EFFECT_MIRROR_COAT); + ASSUME(GetMoveCategory(MOVE_ROUND) == DAMAGE_CATEGORY_SPECIAL); +} + +SINGLE_BATTLE_TEST("Mirror Coat will do twice as much damage received from the opponent") +{ + s16 normalDmg; + s16 mirrorCoatDmg; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_ROUND); MOVE(player, MOVE_MIRROR_COAT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponent); + HP_BAR(player, captureDamage: &normalDmg); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, player); + HP_BAR(opponent, captureDamage: &mirrorCoatDmg); + } THEN { + EXPECT_MUL_EQ(normalDmg, Q_4_12(2.0), mirrorCoatDmg); + } +} + +DOUBLE_BATTLE_TEST("Mirror Coat cannot affect ally Pokémon") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_ROUND, target: playerRight); + MOVE(playerRight, MOVE_MIRROR_COAT, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, playerLeft); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, playerRight); + } +} + +DOUBLE_BATTLE_TEST("Mirror Coat hits the last opponent that hit the user") +{ + s16 normalDmg; + s16 mirrorCoatDmg; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_ROUND, target: playerLeft); + MOVE(opponentRight, MOVE_ROUND, target: playerLeft); + MOVE(playerLeft, MOVE_MIRROR_COAT); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentRight); + HP_BAR(playerLeft, captureDamage: &normalDmg); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, playerLeft); + HP_BAR(opponentRight, captureDamage: &mirrorCoatDmg); + } THEN { + EXPECT_MUL_EQ(normalDmg, Q_4_12(2.0), mirrorCoatDmg); + } +} + +DOUBLE_BATTLE_TEST("Mirror Coat respects Follow Me") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { + MOVE(opponentRight, MOVE_FOLLOW_ME); + MOVE(opponentLeft, MOVE_ROUND, target: playerLeft); + MOVE(playerLeft, MOVE_MIRROR_COAT, target: opponentLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FOLLOW_ME, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, playerLeft); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Mirror Coat fails if mon that damaged Mirror Coat user is no longer on the field (Gen 1-4)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); }; + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_ROUND, target: playerLeft); + MOVE(playerRight, MOVE_ROUND, target: opponentLeft); + MOVE(playerLeft, MOVE_MIRROR_COAT, target: opponentLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, playerRight); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, playerLeft); + } +} + +SINGLE_BATTLE_TEST("Mirror Coat deals 1 damage when the attack received is blocked by Disguise") +{ + s16 mirrorCoatDmg; + GIVEN { + ASSUME(GetMoveCategory(MOVE_HEX) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_MIMIKYU) { Ability(ABILITY_DISGUISE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_HEX); MOVE(player, MOVE_MIRROR_COAT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEX, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, player); + HP_BAR(opponent, captureDamage: &mirrorCoatDmg); + } THEN { + EXPECT_EQ(mirrorCoatDmg, 1); + } +} diff --git a/test/battle/move_flags/recoil.c b/test/battle/move_flags/recoil.c index c2b2e21b86..7a71187c82 100644 --- a/test/battle/move_flags/recoil.c +++ b/test/battle/move_flags/recoil.c @@ -122,3 +122,18 @@ SINGLE_BATTLE_TEST("Recoil: The correct amount of recoil damage is dealt after t EXPECT_MUL_EQ(directDamage, UQ_4_12(0.25), recoilDamage); } } + +SINGLE_BATTLE_TEST("Recoil: No recoil is taken if the move is blocked by Disguise") +{ + GIVEN { + ASSUME(GetMoveRecoil(MOVE_FLARE_BLITZ) > 0); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MIMIKYU) { Ability(ABILITY_DISGUISE); } + } WHEN { + TURN { MOVE(player, MOVE_FLARE_BLITZ); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLARE_BLITZ, player); + } THEN { + EXPECT_EQ(player->hp, player->maxHP); + } +}