From c45566511525d8124663a69bd8211b7f6025cf47 Mon Sep 17 00:00:00 2001 From: Raymond Dodge Date: Wed, 8 Oct 2025 10:03:20 -0400 Subject: [PATCH] Add Struggle tests, weakness berry tests and prevent Struggle from activating Silk Scarf and Chilan Berry (#7880) --- src/battle_main.c | 9 +- test/battle/ability/color_change.c | 20 ++++ test/battle/ability/protean.c | 16 +++ test/battle/hold_effect/gems.c | 28 +++++ test/battle/hold_effect/type_power.c | 24 +++++ test/battle/hold_effect/weakness_berry.c | 124 +++++++++++++++++++++++ test/battle/move_effect/struggle.c | 75 ++++++++++++++ 7 files changed, 291 insertions(+), 5 deletions(-) create mode 100644 test/battle/hold_effect/weakness_berry.c create mode 100644 test/battle/move_effect/struggle.c diff --git a/src/battle_main.c b/src/battle_main.c index 6a45019ff1..26755eda97 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -5862,11 +5862,11 @@ u32 GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, enum MonState enum ItemHoldEffect holdEffect; enum Gimmick gimmick = GetActiveGimmick(battler); - if (move == MOVE_STRUGGLE) - return TYPE_NORMAL; - if (state == MON_IN_BATTLE) { + if (moveEffect == EFFECT_STRUGGLE) + return TYPE_MYSTERY; + species = gBattleMons[battler].species; heldItem = gBattleMons[battler].item; holdEffect = GetBattlerHoldEffect(battler, TRUE); @@ -6119,8 +6119,7 @@ void SetTypeBeforeUsingMove(u32 move, u32 battler) && GetBattleMoveType(move) == GetItemSecondaryId(heldItem) && effect != EFFECT_PLEDGE && effect != EFFECT_OHKO - && effect != EFFECT_SHEER_COLD - && effect != EFFECT_STRUGGLE) + && effect != EFFECT_SHEER_COLD) { gSpecialStatuses[battler].gemParam = GetBattlerHoldEffectParam(battler); gSpecialStatuses[battler].gemBoost = TRUE; diff --git a/test/battle/ability/color_change.c b/test/battle/ability/color_change.c index e097035f62..6ea5d9537b 100644 --- a/test/battle/ability/color_change.c +++ b/test/battle/ability/color_change.c @@ -153,3 +153,23 @@ SINGLE_BATTLE_TEST("Color Change changes the type to Normal when a Pokemon is hi MESSAGE("The opposing Kecleon's Color Change made it the Normal type!"); } } + +SINGLE_BATTLE_TEST("Color Change does not change the type to Normal when a Pokemon is hit by Struggle") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_COLOR_CHANGE); } + } WHEN { + TURN { MOVE(player, MOVE_SOAK); } + TURN { MOVE(player, MOVE_STRUGGLE); } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SOAK, player); + MESSAGE("The opposing Kecleon transformed into the Water type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_COLOR_CHANGE); + MESSAGE("The opposing Kecleon's Color Change made it the Normal type!"); + } + } +} diff --git a/test/battle/ability/protean.c b/test/battle/ability/protean.c index c5d141d244..fe4ae25e60 100644 --- a/test/battle/ability/protean.c +++ b/test/battle/ability/protean.c @@ -54,3 +54,19 @@ SINGLE_BATTLE_TEST("Protean changes the type of the user only once per switch in ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); } } + +SINGLE_BATTLE_TEST("Protean does not change the user's type when using Struggle") +{ + GIVEN { + PLAYER(SPECIES_REGIROCK); + OPPONENT(SPECIES_GRENINJA) { Ability(ABILITY_PROTEAN); } + } WHEN { + TURN { MOVE(opponent, MOVE_STRUGGLE); } + } SCENE { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PROTEAN); + MESSAGE("The opposing Greninja transformed into the Normal type!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, opponent); + } +} diff --git a/test/battle/hold_effect/gems.c b/test/battle/hold_effect/gems.c index b2d843e1d5..b597beeab7 100644 --- a/test/battle/hold_effect/gems.c +++ b/test/battle/hold_effect/gems.c @@ -26,6 +26,34 @@ SINGLE_BATTLE_TEST("Gem is consumed when it corresponds to the type of a move") } } +SINGLE_BATTLE_TEST("Gem is not consumed when using Struggle", s16 damage) +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_NORMAL_GEM; } + + GIVEN { + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_GEMS); + ASSUME(GetItemSecondaryId(item) == GetMoveType(MOVE_STRUGGLE)); + } + PLAYER(SPECIES_WOBBUFFET) { Item(item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The Normal Gem strengthened Wobbuffet's power!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + SINGLE_BATTLE_TEST("Gem boost is only applied once") { s16 boostedHit; diff --git a/test/battle/hold_effect/type_power.c b/test/battle/hold_effect/type_power.c index 5afe2a39c8..c03d2487d5 100644 --- a/test/battle/hold_effect/type_power.c +++ b/test/battle/hold_effect/type_power.c @@ -53,3 +53,27 @@ SINGLE_BATTLE_TEST("Type-enhancing items increase the base power of moves by 20% } } } + +SINGLE_BATTLE_TEST("Type-enhancing items do not increase the power of Struggle", s16 damage) +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_SILK_SCARF; } + + GIVEN { + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_TYPE_POWER); + ASSUME(GetItemSecondaryId(item) == GetMoveType(MOVE_STRUGGLE)); + } + PLAYER(SPECIES_WOBBUFFET) { Item(item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} diff --git a/test/battle/hold_effect/weakness_berry.c b/test/battle/hold_effect/weakness_berry.c new file mode 100644 index 0000000000..17adb08aa3 --- /dev/null +++ b/test/battle/hold_effect/weakness_berry.c @@ -0,0 +1,124 @@ +#include "global.h" +#include "test/battle.h" + +static const u16 sMoveItemTable[][4] = +{ + { TYPE_NORMAL, MOVE_SCRATCH, ITEM_CHILAN_BERRY, SPECIES_WOBBUFFET }, + { TYPE_FIGHTING, MOVE_KARATE_CHOP, ITEM_CHOPLE_BERRY, SPECIES_RAMPARDOS }, + { TYPE_FLYING, MOVE_WING_ATTACK, ITEM_COBA_BERRY, SPECIES_HARIYAMA }, + { TYPE_POISON, MOVE_POISON_STING, ITEM_KEBIA_BERRY, SPECIES_GOGOAT }, + { TYPE_GROUND, MOVE_MUD_SHOT, ITEM_SHUCA_BERRY, SPECIES_RAMPARDOS }, + { TYPE_ROCK, MOVE_ROCK_THROW, ITEM_CHARTI_BERRY, SPECIES_CORVISQUIRE }, + { TYPE_BUG, MOVE_BUG_BITE, ITEM_TANGA_BERRY, SPECIES_WOBBUFFET }, + { TYPE_GHOST, MOVE_SHADOW_PUNCH, ITEM_KASIB_BERRY, SPECIES_WOBBUFFET }, + { TYPE_STEEL, MOVE_METAL_CLAW, ITEM_BABIRI_BERRY, SPECIES_RAMPARDOS }, + { TYPE_FIRE, MOVE_EMBER, ITEM_OCCA_BERRY, SPECIES_GOGOAT }, + { TYPE_WATER, MOVE_WATER_GUN, ITEM_PASSHO_BERRY, SPECIES_RAMPARDOS }, + { TYPE_GRASS, MOVE_VINE_WHIP, ITEM_RINDO_BERRY, SPECIES_RAMPARDOS }, + { TYPE_ELECTRIC, MOVE_THUNDER_SHOCK, ITEM_WACAN_BERRY, SPECIES_CORVISQUIRE }, + { TYPE_PSYCHIC, MOVE_CONFUSION, ITEM_PAYAPA_BERRY, SPECIES_HARIYAMA }, + { TYPE_ICE, MOVE_AURORA_BEAM, ITEM_YACHE_BERRY, SPECIES_DRAGONAIR }, + { TYPE_DRAGON, MOVE_DRAGON_BREATH, ITEM_HABAN_BERRY, SPECIES_DRAGONAIR }, + { TYPE_DARK, MOVE_BITE, ITEM_COLBUR_BERRY, SPECIES_WOBBUFFET }, + { TYPE_FAIRY, MOVE_DISARMING_VOICE, ITEM_ROSELI_BERRY, SPECIES_DRAGONAIR }, +}; + +SINGLE_BATTLE_TEST("Weakness berries decrease the base power of moves by half", s16 damage) +{ + u32 move = 0, item = 0, type = 0, defender = 0; + + for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++) + { + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; defender = sMoveItemTable[j][3]; item = ITEM_NONE; } + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; defender = sMoveItemTable[j][3]; item = sMoveItemTable[j][2]; } + } + + GIVEN { + ASSUME(GetMovePower(move) > 0); + ASSUME(GetMoveType(move) == type); + ASSUME(GetSpeciesType(defender, 0) == GetSpeciesType(defender, 1)); + if (type != TYPE_NORMAL) { + ASSUME(gTypeEffectivenessTable[type][GetSpeciesType(defender, 0)] > UQ_4_12(1.0)); + } + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_RESIST_BERRY); + ASSUME(GetItemHoldEffectParam(item) == type); + } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(defender) { Item(item); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + if (1 == i % 2) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++) { + EXPECT_MUL_EQ(results[j*2].damage, Q_4_12(0.5), results[(j*2)+1].damage); + } + } +} + +SINGLE_BATTLE_TEST("Weakness berries do not activate unless a move is super effective", s16 damage) +{ + u32 move = 0, item = 0, type = 0, defender = 0; + + for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++) + { + if (TYPE_NORMAL == type) + { + // ITEM_CHILAN_BERRY activates without a weakness + } + else if (TYPE_FAIRY == type) + { + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; item = sMoveItemTable[j][2]; defender = SPECIES_WOBBUFFET; } + } + else + { + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; item = sMoveItemTable[j][2]; defender = SPECIES_SABLEYE; } + } + } + + GIVEN { + ASSUME(GetMovePower(move) > 0); + ASSUME(uq4_12_multiply(gTypeEffectivenessTable[type][GetSpeciesType(defender, 0)], + gTypeEffectivenessTable[type][GetSpeciesType(defender, 1)]) <= UQ_4_12(1.0)); + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_RESIST_BERRY); + ASSUME(GetItemHoldEffectParam(item) == type); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(defender) { Item(item); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} + +SINGLE_BATTLE_TEST("Weakness berries do not decrease the power of Struggle", s16 damage) +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_CHILAN_BERRY; } + + GIVEN { + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_RESIST_BERRY); + ASSUME(GetItemHoldEffectParam(item) == TYPE_NORMAL); + } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(item); } + } WHEN { + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The Chilan Berry weakened the damage to the opposing Wobbuffet!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} diff --git a/test/battle/move_effect/struggle.c b/test/battle/move_effect/struggle.c new file mode 100644 index 0000000000..5bf9de6207 --- /dev/null +++ b/test/battle/move_effect/struggle.c @@ -0,0 +1,75 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Struggle deals recoil 1/4 of damage dealt (Gen 2-3)") + +SINGLE_BATTLE_TEST("Struggle deals recoil 1/4 of user's hp (Gen 4+)") +{ + ASSUME(GetMoveEffect(MOVE_STRUGGLE) == EFFECT_STRUGGLE); + + s16 recoil; + u32 atkStat = 0; + u32 hpStat = 0; + + PARAMETRIZE { atkStat = 100; hpStat = 200; } + PARAMETRIZE { atkStat = 50; hpStat = 200; } + PARAMETRIZE { atkStat = 100; hpStat = 300; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MaxHP(hpStat); HP(hpStat); Attack(atkStat); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(player, captureDamage: &recoil); + } THEN { + EXPECT_MUL_EQ(hpStat, Q_4_12(0.25), recoil); + } +} + +SINGLE_BATTLE_TEST("Struggle can hit ghost types") +{ + ASSUME(GetSpeciesType(SPECIES_DRIFBLIM, 0) == TYPE_GHOST); + + s16 damage; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_DRIFBLIM); + } WHEN { + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(opponent, captureDamage: &damage); + } THEN { + EXPECT_NE(0, damage); + } +} + +SINGLE_BATTLE_TEST("Struggle does not receive normal-type STAB") +{ + // Compare with Cut, which does receive normal-type STAB + ASSUME(GetSpeciesType(SPECIES_ZANGOOSE, 0) == GetMoveType(MOVE_STRUGGLE)); + ASSUME(GetMovePower(MOVE_CUT) == GetMovePower(MOVE_STRUGGLE)); + ASSUME(GetMoveCategory(MOVE_CUT) == GetMoveCategory(MOVE_STRUGGLE)); + ASSUME(GetMoveType(MOVE_CUT) == GetMoveType(MOVE_STRUGGLE)); + + s16 cutDamage; + s16 struggleDamage; + + GIVEN { + PLAYER(SPECIES_ZANGOOSE); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CUT); } + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CUT, player); + HP_BAR(opponent, captureDamage: &cutDamage); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(opponent, captureDamage: &struggleDamage); + } THEN { + EXPECT_MUL_EQ(struggleDamage, Q_4_12(1.5), cutDamage); + } +}