From 89cfd85b8fa8a2d5156182541f13aff958889fd5 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 31 Jan 2025 21:49:26 +0100 Subject: [PATCH 1/7] Fixes battler mutation during the intim script (#6151) Co-authored-by: Bassoonian --- data/battle_scripts_1.s | 4 ++++ test/battle/ability/intimidate.c | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 0703dc09c0..bb4e4ea2bd 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -7821,8 +7821,12 @@ BattleScript_IntimidateEffect: printstring STRINGID_PKMNCUTSATTACKWITH BattleScript_IntimidateEffect_WaitString: waitmessage B_WAIT_TIME_LONG + saveattacker + savetarget copybyte sBATTLER, gBattlerTarget call BattleScript_TryIntimidateHoldEffects + restoreattacker + restoretarget BattleScript_IntimidateLoopIncrement: addbyte gBattlerTarget, 1 jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_IntimidateLoop diff --git a/test/battle/ability/intimidate.c b/test/battle/ability/intimidate.c index e0f97d5bda..6aea91de25 100644 --- a/test/battle/ability/intimidate.c +++ b/test/battle/ability/intimidate.c @@ -351,3 +351,27 @@ SINGLE_BATTLE_TEST("Intimidate activates when it's no longer affected by Neutral } } +DOUBLE_BATTLE_TEST("Intimidate will correctly decrease the attack of the second mon after Protosynthesis activated") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WALKING_WAKE) { Ability(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { SWITCH(opponentLeft, 2); SEND_OUT(playerLeft, 2); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PROTOSYNTHESIS); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + } +} From c5c22a0005e2317d37667e7458d1431074457206 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 31 Jan 2025 21:50:26 +0100 Subject: [PATCH 2/7] Fixes Dynamic Moves types in SumScreen while in Battle (#6145) --- src/battle_main.c | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/battle_main.c b/src/battle_main.c index 5028a02e33..43bc13277b 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -5844,11 +5844,12 @@ u32 GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, u8 *ateBoost) u32 moveType = gMovesInfo[move].type; u32 moveEffect = gMovesInfo[move].effect; u32 species, heldItem, holdEffect, ability, type1, type2, type3; + bool32 monInBattle = gMain.inBattle && gPartyMenu.menuType != PARTY_MENU_TYPE_IN_BATTLE; if (move == MOVE_STRUGGLE) return TYPE_NORMAL; - if (gMain.inBattle) + if (monInBattle) { species = gBattleMons[battler].species; heldItem = gBattleMons[battler].item; @@ -5872,18 +5873,21 @@ u32 GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, u8 *ateBoost) switch (moveEffect) { case EFFECT_WEATHER_BALL: - if (gMain.inBattle && WEATHER_HAS_EFFECT) + if (monInBattle) { - if (gBattleWeather & B_WEATHER_RAIN && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA) - return TYPE_WATER; - else if (gBattleWeather & B_WEATHER_SANDSTORM) - return TYPE_ROCK; - else if (gBattleWeather & B_WEATHER_SUN && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA) - return TYPE_FIRE; - else if (gBattleWeather & (B_WEATHER_SNOW | B_WEATHER_HAIL)) - return TYPE_ICE; - else - return moveType; + if (WEATHER_HAS_EFFECT) + { + if (gBattleWeather & B_WEATHER_RAIN && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA) + return TYPE_WATER; + else if (gBattleWeather & B_WEATHER_SANDSTORM) + return TYPE_ROCK; + else if (gBattleWeather & B_WEATHER_SUN && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA) + return TYPE_FIRE; + else if (gBattleWeather & (B_WEATHER_SNOW | B_WEATHER_HAIL)) + return TYPE_ICE; + else + return moveType; + } } else { @@ -5909,7 +5913,7 @@ u32 GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, u8 *ateBoost) case EFFECT_HIDDEN_POWER: { u32 typeBits = 0; - if (gMain.inBattle) + if (monInBattle) { typeBits = ((gBattleMons[battler].hpIV & 1) << 0) | ((gBattleMons[battler].attackIV & 1) << 1) @@ -5985,7 +5989,7 @@ u32 GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, u8 *ateBoost) else return moveType; case EFFECT_TERRAIN_PULSE: - if (gMain.inBattle) + if (monInBattle) { if (IsBattlerTerrainAffected(battler, STATUS_FIELD_TERRAIN_ANY)) { From 97d8bd2646f8cde956a05872ee048d6a7f3ba873 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 31 Jan 2025 22:02:26 +0100 Subject: [PATCH 3/7] Fixes Dragon Tail missing timing against Rocky Helmet / Iron Barbs (#6154) --- asm/macros/battle_script.inc | 5 -- data/battle_scripts_1.s | 29 ++++++----- include/battle_scripts.h | 5 +- include/constants/battle_script_commands.h | 1 + src/battle_script_commands.c | 57 +++++++++++++-------- src/data/battle_move_effects.h | 2 +- test/battle/move_effect/hit_switch_target.c | 49 ++++++++++++++++++ 7 files changed, 109 insertions(+), 39 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index a5e21af674..67d97fda02 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1721,11 +1721,6 @@ .4byte \failInstr .endm - .macro tryhitswitchtarget failInstr:req - callnative BS_TryHitSwitchTarget - .4byte \failInstr - .endm - .macro setmagiccoattarget callnative BS_SetMagicCoatTarget .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index bb4e4ea2bd..88cd06d596 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -1798,25 +1798,17 @@ BattleScript_EffectFinalGambit:: jumpifmovehadnoeffect BattleScript_MoveEnd goto BattleScript_MoveEnd -BattleScript_EffectHitSwitchTarget:: - call BattleScript_EffectHit_Ret - tryfaintmon BS_TARGET - jumpiffainted BS_TARGET, TRUE, BattleScript_MoveEnd - jumpifability BS_TARGET, ABILITY_SUCTION_CUPS, BattleScript_AbilityPreventsPhasingOut - jumpifability BS_TARGET, ABILITY_GUARD_DOG, BattleScript_MoveEnd - jumpifstatus3 BS_TARGET, STATUS3_ROOTED, BattleScript_PrintMonIsRooted - jumpiftargetdynamaxed BattleScript_HitSwitchTargetDynamaxed - tryhitswitchtarget BattleScript_MoveEnd +BattleScript_TryHitSwitchTarget:: forcerandomswitch BattleScript_HitSwitchTargetForceRandomSwitchFailed - goto BattleScript_MoveEnd + return -BattleScript_HitSwitchTargetDynamaxed: +BattleScript_HitSwitchTargetDynamaxed:: printstring STRINGID_MOVEBLOCKEDBYDYNAMAX waitmessage B_WAIT_TIME_LONG BattleScript_HitSwitchTargetForceRandomSwitchFailed: hitswitchtargetfailed setbyte sSWITCH_CASE, B_SWITCH_NORMAL - goto BattleScript_MoveEnd + return BattleScript_EffectToxicThread:: setstatchanger STAT_SPEED, 1, TRUE @@ -6774,6 +6766,12 @@ BattleScript_PrintMonIsRooted:: waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd +BattleScript_PrintMonIsRootedRet:: + pause B_WAIT_TIME_SHORT + printstring STRINGID_PKMNANCHOREDITSELF + waitmessage B_WAIT_TIME_LONG + return + BattleScript_AtkDefDown:: setbyte sSTAT_ANIM_PLAYED, FALSE playstatchangeanimation BS_ATTACKER, BIT_DEF | BIT_ATK, STAT_CHANGE_CANT_PREVENT | STAT_CHANGE_NEGATIVE | STAT_CHANGE_MULTIPLE_STATS @@ -8261,6 +8259,13 @@ BattleScript_AbilityPreventsPhasingOut:: waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd +BattleScript_AbilityPreventsPhasingOutRet:: + pause B_WAIT_TIME_SHORT + call BattleScript_AbilityPopUp + printstring STRINGID_PKMNANCHORSITSELFWITH + waitmessage B_WAIT_TIME_LONG + return + BattleScript_AbilityNoStatLoss:: pause B_WAIT_TIME_SHORT call BattleScript_AbilityPopUp diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 1404a40718..fb830c275b 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -770,7 +770,10 @@ extern const u8 BattleScript_EffectDefenseUp3[]; extern const u8 BattleScript_EffectNobleRoar[]; extern const u8 BattleScript_EffectVenomDrench[]; extern const u8 BattleScript_EffectToxicThread[]; -extern const u8 BattleScript_EffectHitSwitchTarget[]; +extern const u8 BattleScript_TryHitSwitchTarget[]; +extern const u8 BattleScript_HitSwitchTargetDynamaxed[]; +extern const u8 BattleScript_AbilityPreventsPhasingOutRet[]; +extern const u8 BattleScript_PrintMonIsRootedRet[]; extern const u8 BattleScript_EffectFinalGambit[]; extern const u8 BattleScript_EffectAutotomize[]; extern const u8 BattleScript_EffectCopycat[]; diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 20858c2365..c8f6b7cd5b 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -276,6 +276,7 @@ enum MoveEndEffects MOVEEND_ITEM_EFFECTS_TARGET, MOVEEND_MOVE_EFFECTS2, MOVEEND_ITEM_EFFECTS_ALL, + MOVEEND_HIT_SWITCH_TARGET, MOVEEND_KINGSROCK, // These item effects will occur each strike of a multi-hit move MOVEEND_NUM_HITS, MOVEEND_SUBSTITUTE, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 61082d33cd..7a7fddac98 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6077,6 +6077,43 @@ static void Cmd_moveend(void) else gBattleScripting.moveendState++; break; + case MOVEEND_HIT_SWITCH_TARGET: + if (gMovesInfo[gCurrentMove].effect == EFFECT_HIT_SWITCH_TARGET + && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) + && TARGET_TURN_DAMAGED + && IsBattlerAlive(gBattlerTarget) + && IsBattlerAlive(gBattlerAttacker) + && gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_1ST_HIT) + { + u32 targetAbility = GetBattlerAbility(gBattlerTarget); + if (targetAbility == ABILITY_GUARD_DOG) + { + gBattleScripting.moveendState++; + break; + } + + effect = TRUE; + BattleScriptPushCursor(); + if (targetAbility == ABILITY_SUCTION_CUPS) + { + gBattlescriptCurrInstr = BattleScript_AbilityPreventsPhasingOutRet; + } + else if (gStatuses3[gBattlerTarget] & STATUS3_ROOTED) + { + gBattlescriptCurrInstr = BattleScript_PrintMonIsRootedRet; + } + else if (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX) + { + gBattlescriptCurrInstr = BattleScript_HitSwitchTargetDynamaxed; + } + else + { + gBattleScripting.switchCase = B_SWITCH_HIT; + gBattlescriptCurrInstr = BattleScript_TryHitSwitchTarget; + } + } + gBattleScripting.moveendState++; + break; case MOVEEND_KINGSROCK: // King's rock // These effects will occur at each hit in a multi-strike move if (ItemBattleEffects(ITEMEFFECT_KINGSROCK, 0, FALSE)) @@ -17381,26 +17418,6 @@ void BS_JumpIfBlockedBySoundproof(void) } } -void BS_TryHitSwitchTarget(void) -{ - NATIVE_ARGS(const u8 *failInstr); - - if (IsBattlerAlive(gBattlerAttacker) - && IsBattlerAlive(gBattlerTarget) - && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) - && TARGET_TURN_DAMAGED - && gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_1ST_HIT - && GetBattlerAbility(gBattlerTarget) != ABILITY_GUARD_DOG) - { - gBattleScripting.switchCase = B_SWITCH_HIT; - gBattlescriptCurrInstr = cmd->nextInstr; - } - else - { - gBattlescriptCurrInstr = cmd->failInstr; - } -} - void BS_SetMagicCoatTarget(void) { NATIVE_ARGS(); diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index ee281d6fe2..0b6a1dd72f 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -1571,7 +1571,7 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = [EFFECT_HIT_SWITCH_TARGET] = { - .battleScript = BattleScript_EffectHitSwitchTarget, + .battleScript = BattleScript_EffectHit, .battleTvScore = 0, // TODO: Assign points }, diff --git a/test/battle/move_effect/hit_switch_target.c b/test/battle/move_effect/hit_switch_target.c index a899ae0b33..bc026110ee 100644 --- a/test/battle/move_effect/hit_switch_target.c +++ b/test/battle/move_effect/hit_switch_target.c @@ -69,3 +69,52 @@ SINGLE_BATTLE_TEST("Dragon Tail does not fail if replacements fainted") NOT MESSAGE("But it failed!"); } } + +SINGLE_BATTLE_TEST("Dragon Tail switches the target after Rocky Helmet and Iron Barbs") +{ + PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TOGEDEMARU) { Ability(ABILITY_IRON_BARBS); Item(ITEM_ROCKY_HELMET); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Togedemaru's Iron Barbs!"); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Togedemaru's Rocky Helmet!"); + MESSAGE("The opposing Charmander was dragged out!"); + } +} + +SINGLE_BATTLE_TEST("Dragon Tail effect will fails against Guard Dog ability") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_OKIDOGI) { Ability(ABILITY_GUARD_DOG); } + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + NOT MESSAGE("The opposing Charmander was dragged out!"); + } +} + +SINGLE_BATTLE_TEST("Dragon Tail effect will fails against Suction Cups ability") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_OCTILLERY) { Ability(ABILITY_SUCTION_CUPS); } + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + MESSAGE("The opposing Octillery anchors itself with Suction Cups!"); + NOT MESSAGE("The opposing Charmander was dragged out!"); + } +} From 1c821c2ff7db00b87dfbc86a5fb24cbecc11eb21 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Sat, 1 Feb 2025 14:35:11 +0100 Subject: [PATCH 4/7] Tests for Big Pecks (#6158) Co-authored-by: Hedara --- test/battle/ability/big_pecks.c | 116 ++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 test/battle/ability/big_pecks.c diff --git a/test/battle/ability/big_pecks.c b/test/battle/ability/big_pecks.c new file mode 100644 index 0000000000..0c61dd3ba3 --- /dev/null +++ b/test/battle/ability/big_pecks.c @@ -0,0 +1,116 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Big Pecks prevents Defense stage reduction from moves") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_LEER].effect == EFFECT_DEFENSE_DOWN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(player, MOVE_LEER); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_BIG_PECKS); + MESSAGE("The opposing Pidgey's Big Pecks prevents Defense loss!"); + } +} + +SINGLE_BATTLE_TEST("Big Pecks is ignored by Mold Breaker") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_LEER].effect == EFFECT_DEFENSE_DOWN); + PLAYER(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(player, MOVE_LEER); } + } SCENE { + ABILITY_POPUP(player, ABILITY_MOLD_BREAKER); + MESSAGE("Pinsir breaks the mold!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LEER, player); + MESSAGE("The opposing Pidgey's Defense fell!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_BIG_PECKS); + MESSAGE("The opposing Pidgey's Big Pecks prevents Defense loss!"); + } + } +} + +SINGLE_BATTLE_TEST("Big Pecks doesn't prevent Defense stage reduction from moves used by the user") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffectSelf(MOVE_SUPERPOWER, MOVE_EFFECT_ATK_DEF_DOWN) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUPERPOWER); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPERPOWER, opponent); + MESSAGE("The opposing Pidgey's Attack fell!"); + MESSAGE("The opposing Pidgey's Defense fell!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Big Pecks doesn't prevent Topsy-Turvy") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_HARDEN].effect == EFFECT_DEFENSE_UP); + ASSUME(gMovesInfo[MOVE_TOPSY_TURVY].effect == EFFECT_TOPSY_TURVY); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(opponent, MOVE_HARDEN); MOVE(player, MOVE_TOPSY_TURVY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, opponent); + MESSAGE("The opposing Pidgey's Defense rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOPSY_TURVY, player); + MESSAGE("All stat changes on the opposing Pidgey were inverted!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Big Pecks doesn't prevent Spectral Thief from resetting positive Defense stage changes") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_HARDEN].effect == EFFECT_DEFENSE_UP); + ASSUME(MoveHasAdditionalEffect(MOVE_SPECTRAL_THIEF, MOVE_EFFECT_SPECTRAL_THIEF)); + ASSUME(gMovesInfo[MOVE_SOAK].effect == EFFECT_SOAK); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player,MOVE_SOAK); } + TURN { MOVE(opponent, MOVE_HARDEN); MOVE(player, MOVE_SPECTRAL_THIEF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, opponent); + MESSAGE("The opposing Pidgey's Defense rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPECTRAL_THIEF, player); + MESSAGE("Wobbuffet stole the target's boosted stats!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Big Pecks doesn't prevent receiving negative Defense stage changes from Baton Pass") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_LEER].effect == EFFECT_DEFENSE_DOWN); + ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(player, MOVE_LEER); + MOVE(opponent, MOVE_BATON_PASS); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LEER, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, opponent); + MESSAGE("2 sent out Pidgey!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} From 8bb52b572796f469354251d5c241f8baa9ecae22 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Sat, 1 Feb 2025 17:34:17 +0100 Subject: [PATCH 5/7] Water Compaction tests (#6159) Co-authored-by: Hedara Co-authored-by: Eduardo Quezada --- test/battle/ability/water_compaction.c | 61 ++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 test/battle/ability/water_compaction.c diff --git a/test/battle/ability/water_compaction.c b/test/battle/ability/water_compaction.c new file mode 100644 index 0000000000..51297f5a8a --- /dev/null +++ b/test/battle/ability/water_compaction.c @@ -0,0 +1,61 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Water Compaction raises Defense 2 stages when hit by a water type move") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_WATER_GUN].type == TYPE_WATER); + PLAYER(SPECIES_SANDYGAST) { Ability(ABILITY_WATER_COMPACTION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + ABILITY_POPUP(player, ABILITY_WATER_COMPACTION); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Water Compaction raises Defense 2 stages on each hit of a multi-hit Water type move") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_SURGING_STRIKES].type == TYPE_WATER); + ASSUME(gMovesInfo[MOVE_SURGING_STRIKES].strikeCount == 3); + PLAYER(SPECIES_SANDYGAST) { Ability(ABILITY_WATER_COMPACTION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SURGING_STRIKES); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURGING_STRIKES, opponent); + ABILITY_POPUP(player, ABILITY_WATER_COMPACTION); + MESSAGE("Sandygast's Defense sharply rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURGING_STRIKES, opponent); + ABILITY_POPUP(player, ABILITY_WATER_COMPACTION); + MESSAGE("Sandygast's Defense sharply rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURGING_STRIKES, opponent); + ABILITY_POPUP(player, ABILITY_WATER_COMPACTION); + MESSAGE("Sandygast's Defense sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 6); + } +} + +SINGLE_BATTLE_TEST("Water Compaction does not affect damage taken from Water type moves", s16 damage) +{ + u16 ability; + PARAMETRIZE { ability = ABILITY_SAND_VEIL; } + PARAMETRIZE { ability = ABILITY_WATER_COMPACTION; } + GIVEN { + ASSUME(gMovesInfo[MOVE_WATER_GUN].type == TYPE_WATER); + PLAYER(SPECIES_SANDYGAST) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} From 7c6eda21b1f9af04bdf0ee246559b1a5d5d0384f Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Sat, 1 Feb 2025 14:47:36 -0300 Subject: [PATCH 6/7] Added missing in-battle "Move Info" button prompt (#6155) --- .../battle_interface/move_info_window_l.png | Bin 0 -> 291 bytes .../battle_interface/move_info_window_r.png | Bin 0 -> 295 bytes include/battle.h | 1 + include/battle_interface.h | 2 + src/battle_controller_player.c | 5 +- src/battle_interface.c | 99 ++++++++++++++++-- 6 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 graphics/battle_interface/move_info_window_l.png create mode 100644 graphics/battle_interface/move_info_window_r.png diff --git a/graphics/battle_interface/move_info_window_l.png b/graphics/battle_interface/move_info_window_l.png new file mode 100644 index 0000000000000000000000000000000000000000..548acf653bf3ae29820f5ee08e8449599140aeae GIT binary patch literal 291 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dy1_3@Hu8bR_tBae1yrL`(9W^-| zfsE(xzyAFDA1Dt5*~P_PP9V+KuAOV?=>am5omCeBDS?t8zu^C9;H=`NDL}bNo-U3d z7QI&|d-EMo;BdJ+K`r^izvFAW_JGyg44KXMZn9 RnFw?pgQu&X%Q~loCIA$4daVEe literal 0 HcmV?d00001 diff --git a/graphics/battle_interface/move_info_window_r.png b/graphics/battle_interface/move_info_window_r.png new file mode 100644 index 0000000000000000000000000000000000000000..f83e8f7f884a0cbcfe3e0f9f37fd500a63de7ea2 GIT binary patch literal 295 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dy1_3@Hu8bR_tBae1yrL`(9W^-| zfsE(xzyAFDA1Dt5*~P_PP9V+KuAOV?=>am5omCeBDS?t8zu^C9;H=`NDL}bto-U3d z7QI&|-{xZw|N3{AcI99F%;)qaWs4Jsq7_%9pa(l+;NzGtK22{n2o&}) z-{|1U*u!**{XizWS-XBgX#s;ys02f@m~_KR#tD2{i??iwV4M_p*r(0;?t1P`{ROgB zd{*x*V^+WTccq4BfvjK7lM6-HyDb>Q+r#G1JiL?3qGgZgoX(H$_SP5vkF>wQeIily V=AM~sT0j>vc)I$ztaD0e0ss~vcG3U< literal 0 HcmV?d00001 diff --git a/include/battle.h b/include/battle.h index 9a4a57d004..56aad13594 100644 --- a/include/battle.h +++ b/include/battle.h @@ -780,6 +780,7 @@ struct BattleStruct u8 ballSwapped:1; // Used for the last used ball feature u8 throwingPokeBall:1; u8 ballSpriteIds[2]; // item gfx, window gfx + u8 moveInfoSpriteId; // move info, window gfx u8 appearedInBattle; // Bitfield to track which Pokemon appeared in battle. Used for Burmy's form change u8 skyDropTargets[MAX_BATTLERS_COUNT]; // For Sky Drop, to account for if multiple Pokemon use Sky Drop in a double battle. // When using a move which hits multiple opponents which is then bounced by a target, we need to make sure, the move hits both opponents, the one with bounce, and the one without. diff --git a/include/battle_interface.h b/include/battle_interface.h index 3280826ff7..600a9a956d 100644 --- a/include/battle_interface.h +++ b/include/battle_interface.h @@ -128,5 +128,7 @@ void SwapBallToDisplay(bool32 sameBall); void ArrowsChangeColorLastBallCycle(bool32 showArrows); void UpdateAbilityPopup(u8 battlerId); void CategoryIcons_LoadSpritesGfx(void); +void TryToAddMoveInfoWindow(void); +void TryToHideMoveInfoWindow(void); #endif // GUARD_BATTLE_INTERFACE_H diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index fcadd22dc7..77ce6083f9 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -673,6 +673,7 @@ void HandleInputChooseMove(u32 battler) if (JOY_NEW(A_BUTTON) && !gBattleStruct->descriptionSubmenu) { + TryToHideMoveInfoWindow(); PlaySE(SE_SELECT); moveTarget = GetBattlerMoveTargetType(battler, moveInfo->moves[gMoveSelectionCursor[battler]]); @@ -783,6 +784,7 @@ void HandleInputChooseMove(u32 battler) BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, 0xFFFF); HideGimmickTriggerSprite(); PlayerBufferExecCompleted(battler); + TryToHideMoveInfoWindow(); } } else if (JOY_NEW(DPAD_LEFT) && !gBattleStruct->zmove.viewing) @@ -882,7 +884,7 @@ void HandleInputChooseMove(u32 battler) MoveSelectionDisplayMoveType(battler); } } - else if (JOY_NEW(B_MOVE_DESCRIPTION_BUTTON) && B_MOVE_DESCRIPTION_BUTTON != B_LAST_USED_BALL_BUTTON) + else if (JOY_NEW(B_MOVE_DESCRIPTION_BUTTON)) { gBattleStruct->descriptionSubmenu = TRUE; MoveSelectionDisplayMoveDescription(battler); @@ -2133,6 +2135,7 @@ void PlayerHandleChooseMove(u32 battler) InitMoveSelectionsVarsAndStrings(battler); gBattleStruct->gimmick.playerSelect = FALSE; + TryToAddMoveInfoWindow(); AssignUsableZMoves(battler, moveInfo->moves); gBattleStruct->zmove.viable = (gBattleStruct->zmove.possibleZMoves[battler] & (1u << gMoveSelectionCursor[battler])) != 0; diff --git a/src/battle_interface.c b/src/battle_interface.c index 5c514a0d80..0c2aac1898 100644 --- a/src/battle_interface.c +++ b/src/battle_interface.c @@ -206,6 +206,7 @@ static void Task_FreeAbilityPopUpGfx(u8); static void SpriteCB_LastUsedBall(struct Sprite *); static void SpriteCB_LastUsedBallWin(struct Sprite *); +static void SpriteCB_MoveInfoWin(struct Sprite *sprite); static const struct OamData sOamData_64x32 = { @@ -732,6 +733,7 @@ u8 CreateBattlerHealthboxSprites(u8 battlerId) gBattleStruct->ballSpriteIds[0] = MAX_SPRITES; gBattleStruct->ballSpriteIds[1] = MAX_SPRITES; + gBattleStruct->moveInfoSpriteId = MAX_SPRITES; return healthboxLeftSpriteId; } @@ -2874,6 +2876,36 @@ static const struct SpriteTemplate sSpriteTemplate_LastUsedBallWindow = .callback = SpriteCB_LastUsedBallWin }; +#define MOVE_INFO_WINDOW_TAG 0xE722 + +static const struct OamData sOamData_MoveInfoWindow = +{ + .y = 0, + .affineMode = 0, + .objMode = 0, + .mosaic = 0, + .bpp = 0, + .shape = SPRITE_SHAPE(32x32), + .x = 0, + .matrixNum = 0, + .size = SPRITE_SIZE(32x32), + .tileNum = 0, + .priority = 1, + .paletteNum = 0, + .affineParam = 0, +}; + +static const struct SpriteTemplate sSpriteTemplate_MoveInfoWindow = +{ + .tileTag = MOVE_INFO_WINDOW_TAG, + .paletteTag = ABILITY_POP_UP_TAG, + .oam = &sOamData_MoveInfoWindow, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCB_MoveInfoWin +}; + #if B_LAST_USED_BALL_BUTTON == R_BUTTON && B_LAST_USED_BALL_CYCLE == TRUE static const u8 ALIGNED(4) sLastUsedBallWindowGfx[] = INCBIN_U8("graphics/battle_interface/last_used_ball_r_cycle.4bpp"); #elif B_LAST_USED_BALL_CYCLE == TRUE @@ -2888,6 +2920,17 @@ static const struct SpriteSheet sSpriteSheet_LastUsedBallWindow = sLastUsedBallWindowGfx, sizeof(sLastUsedBallWindowGfx), LAST_BALL_WINDOW_TAG }; +#if B_MOVE_DESCRIPTION_BUTTON == R_BUTTON +static const u8 sMoveInfoWindowGfx[] = INCBIN_U8("graphics/battle_interface/move_info_window_r.4bpp"); +#else +static const u8 sMoveInfoWindowGfx[] = INCBIN_U8("graphics/battle_interface/move_info_window_l.4bpp"); +#endif + +static const struct SpriteSheet sSpriteSheet_MoveInfoWindow = +{ + sMoveInfoWindowGfx, sizeof(sMoveInfoWindowGfx), MOVE_INFO_WINDOW_TAG +}; + #define LAST_USED_BALL_X_F 14 #define LAST_USED_BALL_X_0 -14 #define LAST_USED_BALL_Y ((IsDoubleBattle()) ? 78 : 68) @@ -2946,7 +2989,7 @@ void TryAddLastUsedBallItemSprites(void) gBattleStruct->ballSpriteIds[0] = AddItemIconSprite(102, 102, gBallToDisplay); gSprites[gBattleStruct->ballSpriteIds[0]].x = LAST_USED_BALL_X_0; gSprites[gBattleStruct->ballSpriteIds[0]].y = LAST_USED_BALL_Y; - gSprites[gBattleStruct->ballSpriteIds[0]].sHide = FALSE; // restore + gSprites[gBattleStruct->ballSpriteIds[0]].sHide = FALSE; gLastUsedBallMenuPresent = TRUE; gSprites[gBattleStruct->ballSpriteIds[0]].callback = SpriteCB_LastUsedBall; } @@ -2961,7 +3004,8 @@ void TryAddLastUsedBallItemSprites(void) gBattleStruct->ballSpriteIds[1] = CreateSprite(&sSpriteTemplate_LastUsedBallWindow, LAST_BALL_WIN_X_0, LAST_USED_WIN_Y, 5); - gSprites[gBattleStruct->ballSpriteIds[1]].sHide = FALSE; // restore + gSprites[gBattleStruct->ballSpriteIds[1]].sHide = FALSE; + gSprites[gBattleStruct->moveInfoSpriteId].sHide = TRUE; gLastUsedBallMenuPresent = TRUE; } if (B_LAST_USED_BALL_CYCLE == TRUE) @@ -2984,6 +3028,32 @@ static void DestroyLastUsedBallGfx(struct Sprite *sprite) gBattleStruct->ballSpriteIds[0] = MAX_SPRITES; } +void TryToAddMoveInfoWindow(void) +{ + LoadSpritePalette(&sSpritePalette_AbilityPopUp); + if (GetSpriteTileStartByTag(MOVE_INFO_WINDOW_TAG) == 0xFFFF) + LoadSpriteSheet(&sSpriteSheet_MoveInfoWindow); + + if (gBattleStruct->moveInfoSpriteId == MAX_SPRITES) + { + gBattleStruct->moveInfoSpriteId = CreateSprite(&sSpriteTemplate_MoveInfoWindow, LAST_BALL_WIN_X_0, LAST_USED_WIN_Y + 32, 6); + gSprites[gBattleStruct->moveInfoSpriteId].sHide = FALSE; + } +} + +void TryToHideMoveInfoWindow(void) +{ + gSprites[gBattleStruct->moveInfoSpriteId].sHide = TRUE; +} + +static void DestroyMoveInfoWinGfx(struct Sprite *sprite) +{ + FreeSpriteTilesByTag(MOVE_INFO_WINDOW_TAG); + FreeSpritePaletteByTag(ABILITY_POP_UP_TAG); + DestroySprite(sprite); + gBattleStruct->moveInfoSpriteId = MAX_SPRITES; +} + static void SpriteCB_LastUsedBallWin(struct Sprite *sprite) { if (sprite->sHide) @@ -3021,6 +3091,23 @@ static void SpriteCB_LastUsedBall(struct Sprite *sprite) } } +static void SpriteCB_MoveInfoWin(struct Sprite *sprite) +{ + if (sprite->sHide) + { + if (sprite->x != LAST_BALL_WIN_X_0) + sprite->x--; + + if (sprite->x == LAST_BALL_WIN_X_0) + DestroyMoveInfoWinGfx(sprite); + } + else + { + if (sprite->x != LAST_BALL_WIN_X_F) + sprite->x++; + } +} + static void TryHideOrRestoreLastUsedBall(u8 caseId) { if (B_LAST_USED_BALL == FALSE) @@ -3032,16 +3119,16 @@ static void TryHideOrRestoreLastUsedBall(u8 caseId) { case 0: // hide if (gBattleStruct->ballSpriteIds[0] != MAX_SPRITES) - gSprites[gBattleStruct->ballSpriteIds[0]].sHide = TRUE; // hide + gSprites[gBattleStruct->ballSpriteIds[0]].sHide = TRUE; if (gBattleStruct->ballSpriteIds[1] != MAX_SPRITES) - gSprites[gBattleStruct->ballSpriteIds[1]].sHide = TRUE; // hide + gSprites[gBattleStruct->ballSpriteIds[1]].sHide = TRUE; gLastUsedBallMenuPresent = FALSE; break; case 1: // restore if (gBattleStruct->ballSpriteIds[0] != MAX_SPRITES) - gSprites[gBattleStruct->ballSpriteIds[0]].sHide = FALSE; // restore + gSprites[gBattleStruct->ballSpriteIds[0]].sHide = FALSE; if (gBattleStruct->ballSpriteIds[1] != MAX_SPRITES) - gSprites[gBattleStruct->ballSpriteIds[1]].sHide = FALSE; // restore + gSprites[gBattleStruct->ballSpriteIds[1]].sHide = FALSE; gLastUsedBallMenuPresent = TRUE; break; } From a0097ef395a610b564976ca5c77bfdac96981491 Mon Sep 17 00:00:00 2001 From: Bassoonian Date: Sat, 1 Feb 2025 21:46:59 +0100 Subject: [PATCH 7/7] Add Poltchageist family form data (#6163) --- src/data/pokemon/form_species_tables.h | 14 ++++++++++++++ src/data/pokemon/species_info/gen_9_families.h | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/src/data/pokemon/form_species_tables.h b/src/data/pokemon/form_species_tables.h index f2c4560216..ee391a74a0 100644 --- a/src/data/pokemon/form_species_tables.h +++ b/src/data/pokemon/form_species_tables.h @@ -2155,6 +2155,20 @@ static const u16 sGimmighoulFormSpeciesIdTable[] = { }; #endif //P_FAMILY_GIMMIGHOUL +#if P_FAMILY_POLTCHAGEIST +static const u16 sPoltchageistFormSpeciesIdTable[] = { + SPECIES_POLTCHAGEIST_COUNTERFEIT, + SPECIES_POLTCHAGEIST_ARTISAN, + FORM_SPECIES_END, +}; + +static const u16 sSinistchaFormSpeciesIdTable[] = { + SPECIES_SINISTCHA_UNREMARKABLE, + SPECIES_SINISTCHA_MASTERPIECE, + FORM_SPECIES_END, +}; +#endif //P_FAMILY_POLTCHAGEIST + #if P_FAMILY_OGERPON static const u16 sOgerponFormSpeciesIdTable[] = { SPECIES_OGERPON_TEAL, diff --git a/src/data/pokemon/species_info/gen_9_families.h b/src/data/pokemon/species_info/gen_9_families.h index 7402619b9a..d0fa8ab74d 100644 --- a/src/data/pokemon/species_info/gen_9_families.h +++ b/src/data/pokemon/species_info/gen_9_families.h @@ -6986,6 +6986,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .levelUpLearnset = sPoltchageistLevelUpLearnset, .teachableLearnset = sPoltchageistTeachableLearnset, .evolutions = EVOLUTION({EVO_ITEM, ITEM_UNREMARKABLE_TEACUP, SPECIES_SINISTCHA_UNREMARKABLE}), + .formSpeciesIdTable = sPoltchageistFormSpeciesIdTable, }, [SPECIES_POLTCHAGEIST_ARTISAN] = { @@ -7048,6 +7049,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .levelUpLearnset = sPoltchageistLevelUpLearnset, .teachableLearnset = sPoltchageistTeachableLearnset, .evolutions = EVOLUTION({EVO_ITEM, ITEM_MASTERPIECE_TEACUP, SPECIES_SINISTCHA_MASTERPIECE}), + .formSpeciesIdTable = sPoltchageistFormSpeciesIdTable, }, [SPECIES_SINISTCHA_UNREMARKABLE] = @@ -7110,6 +7112,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = ) .levelUpLearnset = sSinistchaLevelUpLearnset, .teachableLearnset = sSinistchaTeachableLearnset, + .formSpeciesIdTable = sSinistchaFormSpeciesIdTable, }, [SPECIES_SINISTCHA_MASTERPIECE] = { @@ -7171,6 +7174,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = ) .levelUpLearnset = sSinistchaLevelUpLearnset, .teachableLearnset = sSinistchaTeachableLearnset, + .formSpeciesIdTable = sSinistchaFormSpeciesIdTable, }, #endif //P_FAMILY_POLTCHAGEIST