From c468c9a5f70ed53b517b84d836843730b27294b4 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Tue, 27 Jan 2026 14:17:26 +0100 Subject: [PATCH 1/8] Fix substitute follower appearing with dead party (#9028) --- src/event_object_movement.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 848289aec7..188f4dc742 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -1971,7 +1971,11 @@ struct Pokemon *GetFirstLiveMon(void) for (i = 0; i < PARTY_SIZE; i++) { struct Pokemon *mon = &gPlayerParty[i]; - if ((OW_FOLLOWERS_ALLOWED_SPECIES && GetMonData(mon, MON_DATA_SPECIES_OR_EGG) != VarGet(OW_FOLLOWERS_ALLOWED_SPECIES)) + u32 species = GetMonData(mon, MON_DATA_SPECIES_OR_EGG); + if (species == SPECIES_NONE) + continue; + + if ((OW_FOLLOWERS_ALLOWED_SPECIES && species != VarGet(OW_FOLLOWERS_ALLOWED_SPECIES)) || (OW_FOLLOWERS_ALLOWED_MET_LVL && GetMonData(mon, MON_DATA_MET_LEVEL) != VarGet(OW_FOLLOWERS_ALLOWED_MET_LVL)) || (OW_FOLLOWERS_ALLOWED_MET_LOC && GetMonData(mon, MON_DATA_MET_LOCATION) != VarGet(OW_FOLLOWERS_ALLOWED_MET_LOC))) continue; From e0fb60ba093fcf444a0d8fe68023a351d05523b0 Mon Sep 17 00:00:00 2001 From: GGbond Date: Tue, 27 Jan 2026 22:39:59 +0800 Subject: [PATCH 2/8] Fix Synchronoise typeless behavior in Gen7+ (#9031) --- src/battle_util.c | 18 ++++++----- test/battle/move_effect/synchronoise.c | 42 ++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/battle_util.c b/src/battle_util.c index c209573d30..f14aed2674 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -9409,19 +9409,23 @@ bool32 TryBattleFormChange(u32 battler, enum FormChanges method) bool32 DoBattlersShareType(u32 battler1, u32 battler2) { s32 i; + s32 j; enum Type types1[3], types2[3]; GetBattlerTypes(battler1, FALSE, types1); GetBattlerTypes(battler2, FALSE, types2); - if (types1[2] == TYPE_MYSTERY) - types1[2] = types1[0]; - if (types2[2] == TYPE_MYSTERY) - types2[2] = types2[0]; - for (i = 0; i < 3; i++) { - if (types1[i] == types2[0] || types1[i] == types2[1] || types1[i] == types2[2]) - return TRUE; + if (types1[i] == TYPE_MYSTERY) + continue; + + for (j = 0; j < 3; j++) + { + if (types2[j] == TYPE_MYSTERY) + continue; + if (types1[i] == types2[j]) + return TRUE; + } } return FALSE; diff --git a/test/battle/move_effect/synchronoise.c b/test/battle/move_effect/synchronoise.c index 45b4f125ea..8201f0e535 100644 --- a/test/battle/move_effect/synchronoise.c +++ b/test/battle/move_effect/synchronoise.c @@ -1,6 +1,17 @@ #include "global.h" #include "test/battle.h" +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_SYNCHRONOISE) == EFFECT_SYNCHRONOISE); + ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 0) == TYPE_PSYCHIC); + ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 1) == TYPE_PSYCHIC); + ASSUME(GetSpeciesType(SPECIES_BULBASAUR, 0) == TYPE_GRASS); + ASSUME(GetSpeciesType(SPECIES_BULBASAUR, 1) == TYPE_POISON); + ASSUME(GetSpeciesType(SPECIES_ARCANINE, 0) == TYPE_FIRE); + ASSUME(GetSpeciesType(SPECIES_ARCANINE, 1) == TYPE_FIRE); +} + DOUBLE_BATTLE_TEST("Synchronoise hits all Pokemon that share a type with the attacker") { GIVEN { @@ -71,4 +82,35 @@ DOUBLE_BATTLE_TEST("Synchronoise will fail if the corresponding typing mon prote } } +DOUBLE_BATTLE_TEST("Synchronoise will fail for a typeless user even if a target is typeless") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_BURN_UP) == EFFECT_FAIL_IF_NOT_ARG_TYPE); + PLAYER(SPECIES_ARCANINE) { Moves(MOVE_BURN_UP, MOVE_SYNCHRONOISE); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ARCANINE) { Moves(MOVE_BURN_UP, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_BURN_UP, target: opponentRight); + MOVE(opponentLeft, MOVE_BURN_UP, target: playerRight); + MOVE(playerRight, MOVE_CELEBRATE); + MOVE(opponentRight, MOVE_CELEBRATE); + } + TURN { + MOVE(playerLeft, MOVE_SYNCHRONOISE); + MOVE(opponentLeft, MOVE_CELEBRATE); + MOVE(playerRight, MOVE_CELEBRATE); + MOVE(opponentRight, MOVE_CELEBRATE); + } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SYNCHRONOISE, playerLeft); + MESSAGE("Arcanine used Synchronoise!"); + MESSAGE("It doesn't affect the opposing Arcanine…"); + MESSAGE("It doesn't affect Wobbuffet…"); + MESSAGE("It doesn't affect the opposing Wobbuffet…"); + NOT MESSAGE("But it failed!"); + } +} + TO_DO_BATTLE_TEST("TODO: Write Synchronoise (Move Effect) test titles") From aafc5814600d6f711eb41593996c75819e4a8e2a Mon Sep 17 00:00:00 2001 From: GGbond Date: Tue, 27 Jan 2026 22:44:42 +0800 Subject: [PATCH 3/8] Fix Psych Up wrong battler message and Gen 6+ crit ratio copying (#9015) --- data/battle_scripts_1.s | 1 + include/config/battle.h | 1 + include/constants/generational_changes.h | 1 + src/battle_script_commands.c | 10 +- test/battle/move_effect/psych_up.c | 120 ++++++++++++++++++++++- 5 files changed, 131 insertions(+), 2 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 7007410e15..37aa8e48b9 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -3767,6 +3767,7 @@ BattleScript_EffectBellyDrum:: BattleScript_EffectPsychUp:: attackcanceler + accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE copyfoestats attackanimation waitanimation diff --git a/include/config/battle.h b/include/config/battle.h index 9d2814cb85..0d02e2873a 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -90,6 +90,7 @@ #define B_MINIMIZE_EVASION GEN_LATEST // In Gen5+, Minimize raises evasion by 2 stages instead of 1. #define B_GROWTH_STAT_RAISE GEN_LATEST // In Gen5+, Growth raises Attack in addition to Special Attack by 1 stage each. Under the effects of the sun, it raises them by 2 stages each instead. #define B_FOCUS_ENERGY_CRIT_RATIO GEN_LATEST // In Gen3+, Focus Energy increases critical hit ratio by 2 instead of 1. +#define B_PSYCH_UP_CRIT_RATIO GEN_LATEST // In Gen6+, Psych Up also copies the target's critical hit ratio. // Other move settings #define B_INCINERATE_GEMS GEN_LATEST // In Gen6+, Incinerate can destroy Gems. diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index ad700a6ea2..37b2b3f357 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -84,6 +84,7 @@ F(MINIMIZE_EVASION, minimizeEvasion, (u32, GEN_COUNT - 1)) \ F(GROWTH_STAT_RAISE, growthStatRaise, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(FOCUS_ENERGY_CRIT_RATIO, focusEnergyCritRatio, (u32, GEN_COUNT - 1)) \ + F(PSYCH_UP_CRIT_RATIO, psychUpCritRatio, (u32, GEN_COUNT - 1)) \ /* Other move settings */ \ F(INCINERATE_GEMS, incinerateGems, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(CAN_SPITE_FAIL, canSpiteFail, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 1421eaf9e6..c25cdf14b6 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -12288,7 +12288,15 @@ static void Cmd_copyfoestats(void) { gBattleMons[gBattlerAttacker].statStages[i] = gBattleMons[gBattlerTarget].statStages[i]; } - gBattleScripting.battler = gBattlerTarget; + if (GetConfig(CONFIG_PSYCH_UP_CRIT_RATIO) >= GEN_6) + { + // Copy crit boosts (Focus Energy, Dragon Cheer, G-Max Chi Strike) + gBattleMons[gBattlerAttacker].volatiles.focusEnergy = gBattleMons[gBattlerTarget].volatiles.focusEnergy; + gBattleMons[gBattlerAttacker].volatiles.dragonCheer = gBattleMons[gBattlerTarget].volatiles.dragonCheer; + gBattleMons[gBattlerAttacker].volatiles.bonusCritStages = gBattleMons[gBattlerTarget].volatiles.bonusCritStages; + } + gEffectBattler = gBattlerTarget; + gBattleScripting.battler = gBattlerAttacker; gBattlescriptCurrInstr = cmd->nextInstr; } diff --git a/test/battle/move_effect/psych_up.c b/test/battle/move_effect/psych_up.c index 36241a4ed7..ca2ecd3f5a 100644 --- a/test/battle/move_effect/psych_up.c +++ b/test/battle/move_effect/psych_up.c @@ -1,4 +1,122 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Psych Up (Move Effect) test titles") +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_PSYCH_UP) == EFFECT_PSYCH_UP); +} + +SINGLE_BATTLE_TEST("Psych Up displays the correct battlers when used by the player") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2); + PLAYER(SPECIES_TORNADUS) { Speed(66); } + OPPONENT(SPECIES_LANDORUS) { Speed(99); } + } WHEN { + TURN { MOVE(opponent, MOVE_SWORDS_DANCE); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_PSYCH_UP); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + MESSAGE("Tornadus copied the opposing Landorus's stat changes!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], opponent->statStages[STAT_ATK]); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Psych Up displays the correct battlers when used by the opponent") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2); + PLAYER(SPECIES_TORNADUS) { Speed(66); } + OPPONENT(SPECIES_LANDORUS) { Speed(99); } + } WHEN { + TURN { MOVE(player, MOVE_SWORDS_DANCE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(opponent, MOVE_PSYCH_UP); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + MESSAGE("The opposing Landorus copied Tornadus's stat changes!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], player->statStages[STAT_ATK]); + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Psych Up ignores Spiky Shield and Baneful Bunker but fails against Crafty Shield") +{ + u32 protectMove = MOVE_NONE; + bool32 shouldFail = FALSE; + + PARAMETRIZE { protectMove = MOVE_SPIKY_SHIELD; shouldFail = FALSE; } + PARAMETRIZE { protectMove = MOVE_BANEFUL_BUNKER; shouldFail = FALSE; } + PARAMETRIZE { protectMove = MOVE_CRAFTY_SHIELD; shouldFail = TRUE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2); + ASSUME(GetMoveEffect(MOVE_SPIKY_SHIELD) == EFFECT_PROTECT); + ASSUME(GetMoveEffect(MOVE_BANEFUL_BUNKER) == EFFECT_PROTECT); + ASSUME(GetMoveEffect(MOVE_CRAFTY_SHIELD) == EFFECT_PROTECT); + PLAYER(SPECIES_TORNADUS) { Speed(66); } + OPPONENT(SPECIES_LANDORUS) { Speed(99); } + } WHEN { + TURN { MOVE(opponent, MOVE_SWORDS_DANCE); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(opponent, protectMove); MOVE(player, MOVE_PSYCH_UP); } + } SCENE { + if (shouldFail) { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCH_UP, player); + MESSAGE("Tornadus copied the opposing Landorus's stat changes!"); + } + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCH_UP, player); + MESSAGE("Tornadus copied the opposing Landorus's stat changes!"); + } + } THEN { + if (shouldFail) { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } else { + EXPECT_EQ(player->statStages[STAT_ATK], opponent->statStages[STAT_ATK]); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } + } +} + +SINGLE_BATTLE_TEST("Psych Up does not copy the target's critical hit ratio (Gen5)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FOCUS_ENERGY) == EFFECT_FOCUS_ENERGY); + WITH_CONFIG(CONFIG_PSYCH_UP_CRIT_RATIO, GEN_5); + WITH_CONFIG(CONFIG_FOCUS_ENERGY_CRIT_RATIO, GEN_9); + PLAYER(SPECIES_TORNADUS) { Speed(66); } + OPPONENT(SPECIES_LANDORUS) { Speed(99); } + } WHEN { + TURN { MOVE(opponent, MOVE_FOCUS_ENERGY); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_PSYCH_UP); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCH_UP, player); + MESSAGE("Tornadus copied the opposing Landorus's stat changes!"); + } THEN { + EXPECT(opponent->volatiles.focusEnergy); + EXPECT(!player->volatiles.focusEnergy); + } +} + +SINGLE_BATTLE_TEST("Psych Up copies the target's critical hit ratio (Gen6+)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FOCUS_ENERGY) == EFFECT_FOCUS_ENERGY); + WITH_CONFIG(CONFIG_PSYCH_UP_CRIT_RATIO, GEN_6); + WITH_CONFIG(CONFIG_FOCUS_ENERGY_CRIT_RATIO, GEN_9); + PLAYER(SPECIES_TORNADUS) { Speed(66); } + OPPONENT(SPECIES_LANDORUS) { Speed(99); } + } WHEN { + TURN { MOVE(opponent, MOVE_FOCUS_ENERGY); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_PSYCH_UP); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCH_UP, player); + MESSAGE("Tornadus copied the opposing Landorus's stat changes!"); + } THEN { + EXPECT(opponent->volatiles.focusEnergy); + EXPECT(player->volatiles.focusEnergy); + } +} From c8eb55969e0923008b25981bf2079a81f075e99c Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:17:25 +0100 Subject: [PATCH 4/8] Add accuracy check to BattleScript_EffectNightmare (#9032) --- data/battle_scripts_1.s | 1 + 1 file changed, 1 insertion(+) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 37aa8e48b9..4f0645838b 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -3393,6 +3393,7 @@ BattleScript_EffectMeanLookGen5: BattleScript_EffectNightmare:: attackcanceler jumpifsubstituteblocks BattleScript_ButItFailed + accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE jumpifvolatile BS_TARGET, VOLATILE_NIGHTMARE, BattleScript_ButItFailed jumpifstatus BS_TARGET, STATUS1_SLEEP, BattleScript_NightmareWorked jumpifability BS_TARGET, ABILITY_COMATOSE, BattleScript_NightmareWorked From b88554198e2b723b029f3cf209bd0580e188f660 Mon Sep 17 00:00:00 2001 From: Frank DeBlasio <35279583+fdeblasio@users.noreply.github.com> Date: Tue, 27 Jan 2026 20:53:23 -0500 Subject: [PATCH 5/8] Fixed instances of COMBO_STARTER_CHARGE used incorrectly (#9034) --- src/data/moves_info.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 31db4e424f..fe61bd32d1 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -9214,8 +9214,8 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = #endif .contestEffect = CONTEST_EFFECT_USER_MORE_EASILY_STARTLED, .contestCategory = CONTEST_CATEGORY_COOL, - .contestComboStarterId = COMBO_STARTER_CHARGE, - .contestComboMoves = {0}, + .contestComboStarterId = 0, + .contestComboMoves = {COMBO_STARTER_CHARGE}, .battleAnimScript = gBattleAnimMove_VoltTackle, .validApprenticeMove = TRUE, }, @@ -13510,8 +13510,8 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .category = DAMAGE_CATEGORY_SPECIAL, .contestEffect = CONTEST_EFFECT_AVOID_STARTLE, //CONTEST_EFFECT_QUICKLY_GROW_BORED .contestCategory = CONTEST_CATEGORY_COOL, - .contestComboStarterId = COMBO_STARTER_CHARGE, - .contestComboMoves = {0}, + .contestComboStarterId = 0, + .contestComboMoves = {COMBO_STARTER_CHARGE}, .battleAnimScript = gBattleAnimMove_VoltSwitch, }, From ec9ba22f8721e256848ef9c5d1b26b8a72cc0613 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Wed, 28 Jan 2026 15:47:07 +0100 Subject: [PATCH 6/8] Fixes to Commander and Emergency Exit (#9040) --- src/battle_script_commands.c | 6 ++- src/battle_util.c | 1 + test/battle/ability/commander.c | 19 ++++++++ test/battle/ability/emergency_exit.c | 70 ++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index c25cdf14b6..d81760a442 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2851,7 +2851,7 @@ static void Cmd_printselectionstringfromtable(void) bool32 HasBattlerActedThisTurn(u32 battler) { u32 i; - for (i = 0; i < gCurrentTurnActionNumber; i++) + for (i = 0; i <= gCurrentTurnActionNumber; i++) { if (gBattlerByTurnOrder[i] == battler) return TRUE; @@ -6848,7 +6848,9 @@ static void Cmd_moveend(void) for (i = 0; i < gBattlersCount; i++) { - if (IsBattlerTurnDamaged(i) && EmergencyExitCanBeTriggered(i)) + if (!IsBattleMoveStatus(gCurrentMove) + && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) + && EmergencyExitCanBeTriggered(i)) { emergencyExitBattlers |= 1u << i; numEmergencyExitBattlers++; diff --git a/src/battle_util.c b/src/battle_util.c index f14aed2674..37280e12b6 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4602,6 +4602,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab && IsBattlerAlive(battler) && gBattleStruct->battlerState[partner].commanderSpecies == SPECIES_NONE && gBattleMons[partner].species == SPECIES_DONDOZO + && (gChosenActionByBattler[partner] != B_ACTION_SWITCH || HasBattlerActedThisTurn(partner)) && GET_BASE_SPECIES_ID(GetMonData(GetBattlerMon(battler), MON_DATA_SPECIES)) == SPECIES_TATSUGIRI) { SaveBattlerAttacker(gBattlerAttacker); diff --git a/test/battle/ability/commander.c b/test/battle/ability/commander.c index 5a075a8ebf..e33c4e1521 100644 --- a/test/battle/ability/commander.c +++ b/test/battle/ability/commander.c @@ -455,3 +455,22 @@ DOUBLE_BATTLE_TEST("Commander prevent Dondozo from switch out by Dragon Tail") NOT MESSAGE("Wobbuffet was dragged out!"); } } + +DOUBLE_BATTLE_TEST("Commander will not activate if partner Dondozo is about to switch out") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_DONDOZO); + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_COMMANDER); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + SWITCH(playerLeft, 2); + SWITCH(playerRight, 3); + } + } SCENE { + NOT ABILITY_POPUP(playerRight, ABILITY_COMMANDER); + } +} diff --git a/test/battle/ability/emergency_exit.c b/test/battle/ability/emergency_exit.c index a03a4853cb..6112267aa5 100644 --- a/test/battle/ability/emergency_exit.c +++ b/test/battle/ability/emergency_exit.c @@ -209,3 +209,73 @@ WILD_BATTLE_TEST("Emergency Exit activates when taking residual damage and falli EXPECT_EQ(gBattleOutcome, B_OUTCOME_PLAYER_TELEPORTED); } } + +SINGLE_BATTLE_TEST("Emergency Exit will trigger due to recoil damage") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_MIND_BLOWN) == EFFECT_MAX_HP_50_RECOIL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_MIND_BLOWN); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIND_BLOWN, opponent); + HP_BAR(player); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit will trigger due to confusion damage") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(133); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_CONFUSE_RAY); + MOVE(opponent, MOVE_POUND); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, opponent); + HP_BAR(opponent); + NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit is not triggered by Pain Split") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_PAIN_SPLIT) == EFFECT_PAIN_SPLIT); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(133); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PAIN_SPLIT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PAIN_SPLIT, player); + HP_BAR(opponent); + NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit will trigger due to Jump Kick recoil") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_JUMP_KICK) == EFFECT_RECOIL_IF_MISS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_JUMP_KICK, hit: FALSE); SEND_OUT(opponent, 1); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_JUMP_KICK, opponent); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + From edaa838a203c845e4bbe96114b539bacea39ad78 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Wed, 28 Jan 2026 15:50:06 +0100 Subject: [PATCH 7/8] Fix berry tree spawning with max yield when OW_BERRY_ALWAYS_WATERABLE is active (#8976) Co-authored-by: Bassoonian --- src/berry.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/berry.c b/src/berry.c index cb8a06ef5b..c6d542a4a6 100644 --- a/src/berry.c +++ b/src/berry.c @@ -20,6 +20,7 @@ static u8 BerryTreeGetNumStagesWatered(struct BerryTree *tree); static u8 GetNumStagesWateredByBerryTreeId(u8 id); static u8 CalcBerryYieldInternal(u16 max, u16 min, u8 water); static u8 CalcBerryYield(struct BerryTree *tree); +static u32 GetBerryTreeAge(u8 id, u8 stage); static u8 GetBerryCountByBerryTreeId(u8 id); static u16 GetStageDurationByBerryType(u8); static u8 GetDrainRateByBerryType(u8); @@ -1961,8 +1962,15 @@ void PlantBerryTree(u8 id, u8 berry, u8 stage, bool8 allowGrowth) tree->stage = stage; tree->moistureLevel = 100; if (OW_BERRY_ALWAYS_WATERABLE) - tree->berryYield = GetBerryInfo(berry)->maxYield; - if (stage == BERRY_STAGE_BERRIES) + { + // We simulate a tree having grown without water + u32 berryTreeAge = GetBerryTreeAge(id, stage); + if (GetBerryInfo(berry)->maxYield - berryTreeAge * GetBerryInfo(berry)->maxYield / 5 < GetBerryInfo(berry)->minYield) + tree->berryYield = GetBerryInfo(berry)->minYield; + else + tree->berryYield = GetBerryInfo(berry)->maxYield - berryTreeAge * GetBerryInfo(berry)->maxYield / 5; + } + else if (stage == BERRY_STAGE_BERRIES) { tree->berryYield = CalcBerryYield(tree); tree->minutesUntilNextStage *= ((tree->mulch == ITEM_TO_MULCH(ITEM_STABLE_MULCH)) ? 6 : 4); @@ -2094,6 +2102,17 @@ static u8 CalcBerryYield(struct BerryTree *tree) return result; } +static u32 GetBerryTreeAge(u8 id, u8 stage) +{ + if (stage == BERRY_STAGE_TRUNK) + stage = 5; + else if (stage == BERRY_STAGE_BUDDING) + stage = 6; + else if (stage > 0) + stage -= 1; + return GetBerryInfo(id)->growthDuration * stage / (OW_BERRY_SIX_STAGES ? 6 : 4); +} + static u8 GetBerryCountByBerryTreeId(u8 id) { return gSaveBlock1Ptr->berryTrees[id].berryYield; @@ -2331,7 +2350,6 @@ static const u8 sBerryMutations[][3] = { {ITEM_TO_BERRY(ITEM_IAPAPA_BERRY), ITEM_TO_BERRY(ITEM_MAGO_BERRY), ITEM_TO_BERRY(ITEM_POMEG_BERRY)}, {ITEM_TO_BERRY(ITEM_CHESTO_BERRY), ITEM_TO_BERRY(ITEM_PERSIM_BERRY), ITEM_TO_BERRY(ITEM_KELPSY_BERRY)}, {ITEM_TO_BERRY(ITEM_ORAN_BERRY), ITEM_TO_BERRY(ITEM_PECHA_BERRY), ITEM_TO_BERRY(ITEM_QUALOT_BERRY)}, - {ITEM_TO_BERRY(ITEM_CHESTO_BERRY), ITEM_TO_BERRY(ITEM_PERSIM_BERRY), ITEM_TO_BERRY(ITEM_KELPSY_BERRY)}, {ITEM_TO_BERRY(ITEM_ASPEAR_BERRY), ITEM_TO_BERRY(ITEM_LEPPA_BERRY), ITEM_TO_BERRY(ITEM_HONDEW_BERRY)}, {ITEM_TO_BERRY(ITEM_AGUAV_BERRY), ITEM_TO_BERRY(ITEM_FIGY_BERRY), ITEM_TO_BERRY(ITEM_GREPA_BERRY)}, {ITEM_TO_BERRY(ITEM_LUM_BERRY), ITEM_TO_BERRY(ITEM_SITRUS_BERRY), ITEM_TO_BERRY(ITEM_TAMATO_BERRY)}, From dd21d8fae4438b7a6c2c8f6c22009fd19223f3ec Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:50:45 +0000 Subject: [PATCH 8/8] Fixes Fling being usable with reusable TMs (#8906) --- src/battle_util.c | 1 + test/battle/ability/cheek_pouch.c | 2 +- test/battle/ability/corrosion.c | 31 ++------------------------ test/battle/move_effect/fling.c | 36 +++++++++---------------------- 4 files changed, 14 insertions(+), 56 deletions(-) diff --git a/src/battle_util.c b/src/battle_util.c index 37280e12b6..bc97de9362 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -9810,6 +9810,7 @@ bool32 CanFling(u32 battler) || (GetConfig(CONFIG_KLUTZ_FLING_INTERACTION) >= GEN_5 && GetBattlerAbility(battler) == ABILITY_KLUTZ) || gFieldStatuses & STATUS_FIELD_MAGIC_ROOM || gBattleMons[battler].volatiles.embargo + || (GetItemTMHMIndex(item) != 0 && GetItemImportance(item) == 1) // don't fling reusable TMs || GetFlingPowerFromItemId(item) == 0 || !CanBattlerGetOrLoseItem(battler, item)) return FALSE; diff --git a/test/battle/ability/cheek_pouch.c b/test/battle/ability/cheek_pouch.c index 0e52ca3fe3..8109dbe17a 100644 --- a/test/battle/ability/cheek_pouch.c +++ b/test/battle/ability/cheek_pouch.c @@ -119,7 +119,7 @@ SINGLE_BATTLE_TEST("Cheek Pouch doesn't activate when using Natural Gift") } } -SINGLE_BATTLE_TEST("Cheek Pouch doesn't activate when using Fling") +SINGLE_BATTLE_TEST("Cheek Pouch doesn't activate when user uses Fling") { GIVEN { ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); diff --git a/test/battle/ability/corrosion.c b/test/battle/ability/corrosion.c index 75ac0fc4bb..c1e56ac596 100644 --- a/test/battle/ability/corrosion.c +++ b/test/battle/ability/corrosion.c @@ -66,34 +66,7 @@ SINGLE_BATTLE_TEST("Corrosion does not effect poison type damaging moves if the } } -SINGLE_BATTLE_TEST("Corrosion can poison Poison- and Steel-type targets if it uses Fling while holding a Toxic Orb or a Poison Barb") -{ - u16 heldItem; - - PARAMETRIZE { heldItem = ITEM_POISON_BARB; } - PARAMETRIZE { heldItem = ITEM_TOXIC_ORB; } - - GIVEN { - ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); - ASSUME(gItemsInfo[ITEM_POISON_BARB].holdEffect == HOLD_EFFECT_TYPE_POWER); - ASSUME(gItemsInfo[ITEM_POISON_BARB].secondaryId == TYPE_POISON); - ASSUME(gItemsInfo[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB); - PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); Item(heldItem); } - OPPONENT(SPECIES_ODDISH); - } WHEN { - TURN { MOVE(player, MOVE_FLING); } - } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); - HP_BAR(opponent); - ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); - if (heldItem == ITEM_POISON_BARB) - STATUS_ICON(opponent, poison: TRUE); - else - STATUS_ICON(opponent, badPoison: TRUE); - } -} - -SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion holds a Toxic Orb, it will badly poison itself") +SINGLE_BATTLE_TEST("Corrosion badly poisons its Poison/Steel-type user who holds a Toxic Orb") { GIVEN { ASSUME(gItemsInfo[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB); @@ -107,7 +80,7 @@ SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion holds a T } } -SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion poisons a target with Synchronize, Synchronize will not poison Poison- or Steel-type Pokémon") +SINGLE_BATTLE_TEST("Corrosion can poison a target with Synchronize and Synchronize will not poison Poison- or Steel-type Pokémon") { u16 move; PARAMETRIZE { move = MOVE_TOXIC; } diff --git a/test/battle/move_effect/fling.c b/test/battle/move_effect/fling.c index 595938bef5..440d43ccb6 100644 --- a/test/battle/move_effect/fling.c +++ b/test/battle/move_effect/fling.c @@ -507,7 +507,7 @@ SINGLE_BATTLE_TEST("Fling deals damage based on items fling power") } } -SINGLE_BATTLE_TEST("Fling deals damage based on a TM's move power") +SINGLE_BATTLE_TEST("Fling deals damage based on a TM's move power if reusable or fails if breakable") { s16 damage[2]; @@ -520,33 +520,17 @@ SINGLE_BATTLE_TEST("Fling deals damage based on a TM's move power") TURN { MOVE(player, MOVE_FLING); } TURN { MOVE(player, MOVE_EGG_BOMB); } } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); - HP_BAR(opponent, captureDamage: &damage[0]); + if (GetItemImportance(ITEM_TM_EARTHQUAKE) == 0) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent, captureDamage: &damage[0]); + } else { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + MESSAGE("But it failed!"); + } ANIMATION(ANIM_TYPE_MOVE, MOVE_EGG_BOMB, player); HP_BAR(opponent, captureDamage: &damage[1]); } THEN { - EXPECT_EQ(damage[0], damage[1]); - } -} - -SINGLE_BATTLE_TEST("Fling deals damage based on a TM's move power") -{ - s16 damage[2]; - - GIVEN { - ASSUME(GetMovePower(MOVE_EARTHQUAKE) == GetMovePower(MOVE_EGG_BOMB)); - ASSUME(!IsSpeciesOfType(SPECIES_WOBBUFFET, TYPE_DARK)); - PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_TM_EARTHQUAKE); } - OPPONENT(SPECIES_HIPPOWDON); - } WHEN { - TURN { MOVE(player, MOVE_FLING); } - TURN { MOVE(player, MOVE_EGG_BOMB); } - } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); - HP_BAR(opponent, captureDamage: &damage[0]); - ANIMATION(ANIM_TYPE_MOVE, MOVE_EGG_BOMB, player); - HP_BAR(opponent, captureDamage: &damage[1]); - } THEN { - EXPECT_EQ(damage[0], damage[1]); + if (GetItemImportance(ITEM_TM_EARTHQUAKE) == 0) + EXPECT_EQ(damage[0], damage[1]); } }