From 712e08775434462ef46250aa4253cd1f49c146bd Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Wed, 29 Oct 2025 17:53:41 +0100 Subject: [PATCH 01/12] Fix move description prompt window not appear when choosing a move after canceling target selection (#8055) --- src/battle_controller_player.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index b76a72af9b..f2819db600 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -460,6 +460,7 @@ void HandleInputChooseTarget(u32 battler) gBattleStruct->zmove.viewing = TRUE; ReloadMoveNames(battler); } + TryToAddMoveInfoWindow(); DoBounceEffect(battler, BOUNCE_HEALTHBOX, 7, 1); DoBounceEffect(battler, BOUNCE_MON, 7, 1); EndBounceEffect(gMultiUsePlayerCursor, BOUNCE_HEALTHBOX); From ea3b72f43fded58ce809c9d2e8169fb5a2853289 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:04:56 +0100 Subject: [PATCH 02/12] Initialize DamageContext on declaration to zero (#8076) --- src/battle_ai_util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 5913c0842c..4a397719c0 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -897,7 +897,7 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u gBattleStruct->magnitudeBasePower = 70; gBattleStruct->presentBasePower = 80; - struct DamageContext ctx; + struct DamageContext ctx = {0}; ctx.battlerAtk = battlerAtk; ctx.battlerDef = battlerDef; ctx.move = move; From 9bfac0099e02795e80564db256aeb93e17f38b47 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Wed, 29 Oct 2025 18:13:41 -0300 Subject: [PATCH 03/12] Fixed fainting form change tests --- include/test/battle.h | 1 + test/battle/form_change/faint.c | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/include/test/battle.h b/include/test/battle.h index 9cd1d23b1f..bf7d22788d 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -935,6 +935,7 @@ struct TestAIScoreStruct bool8 explicitTarget; }; +// Party data at the beginning of the test. Shouldn't be confused with the party data changed during the test. #define PLAYER_PARTY (gBattleTestRunnerState->data.recordedBattle.playerParty) #define OPPONENT_PARTY (gBattleTestRunnerState->data.recordedBattle.opponentParty) diff --git a/test/battle/form_change/faint.c b/test/battle/form_change/faint.c index 8fc95cdc65..316ecca0bf 100644 --- a/test/battle/form_change/faint.c +++ b/test/battle/form_change/faint.c @@ -1,9 +1,24 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("Aegislash reverts to Shield Form upon fainting") +SINGLE_BATTLE_TEST("Aegislash reverts to Shield Form upon fainting (start as Shield)") +{ + GIVEN { + PLAYER(SPECIES_AEGISLASH_SHIELD) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_GUST); SEND_OUT(player, 1); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Gust!"); + MESSAGE("Aegislash fainted!"); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_AEGISLASH_SHIELD); + } +} + +SINGLE_BATTLE_TEST("Aegislash reverts to Shield Form upon fainting (start as Blade)") { - KNOWN_FAILING; GIVEN { PLAYER(SPECIES_AEGISLASH_BLADE) { HP(1); } PLAYER(SPECIES_WOBBUFFET); @@ -14,7 +29,7 @@ SINGLE_BATTLE_TEST("Aegislash reverts to Shield Form upon fainting") MESSAGE("The opposing Wobbuffet used Gust!"); MESSAGE("Aegislash fainted!"); } THEN { - EXPECT_EQ(GetMonData(&PLAYER_PARTY[0], MON_DATA_SPECIES), SPECIES_AEGISLASH_SHIELD); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_AEGISLASH_SHIELD); } } From 9fa857d34cb0c05565b9dd7be6c0e480e16de6c2 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Wed, 29 Oct 2025 21:08:05 -0300 Subject: [PATCH 04/12] Removed redundant comment --- include/test/battle.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/test/battle.h b/include/test/battle.h index bf7d22788d..9cd1d23b1f 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -935,7 +935,6 @@ struct TestAIScoreStruct bool8 explicitTarget; }; -// Party data at the beginning of the test. Shouldn't be confused with the party data changed during the test. #define PLAYER_PARTY (gBattleTestRunnerState->data.recordedBattle.playerParty) #define OPPONENT_PARTY (gBattleTestRunnerState->data.recordedBattle.opponentParty) From 814cf53629d9f386fdb9da227638a3332f1181c2 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Thu, 30 Oct 2025 11:43:50 -0300 Subject: [PATCH 05/12] Fixed Hunger Switch changing forms on switch out while Tera'd (#8080) --- src/battle_util.c | 28 ++++++++++++++++++++-------- test/battle/ability/hunger_switch.c | 1 - 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/battle_util.c b/src/battle_util.c index 2e2f18cbc9..182910aadc 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -10104,14 +10104,26 @@ bool32 CanBattlerFormChange(u32 battler, enum FormChanges method) if (gBattleMons[battler].volatiles.transformed && B_TRANSFORM_FORM_CHANGES >= GEN_5) return FALSE; - // Mega Evolved and Ultra Bursted Pokémon should always revert to normal upon fainting or ending the battle. - if ((IsBattlerMegaEvolved(battler) || IsBattlerUltraBursted(battler) || IsBattlerInTeraForm(battler)) && (method == FORM_CHANGE_FAINT || method == FORM_CHANGE_END_BATTLE)) - return TRUE; - else if (IsBattlerPrimalReverted(battler) && (method == FORM_CHANGE_END_BATTLE)) - return TRUE; - // Gigantamaxed Pokemon should revert upon fainting, switching, or ending the battle. - else if (IsGigantamaxed(battler) && (method == FORM_CHANGE_FAINT || method == FORM_CHANGE_BATTLE_SWITCH || method == FORM_CHANGE_END_BATTLE)) - return TRUE; + + switch (method) + { + case FORM_CHANGE_END_BATTLE: + if (IsBattlerPrimalReverted(battler)) + return TRUE; + // Fallthrough + case FORM_CHANGE_FAINT: + if (IsBattlerMegaEvolved(battler) || IsBattlerUltraBursted(battler) || IsBattlerInTeraForm(battler) || IsGigantamaxed(battler)) + return TRUE; + break; + case FORM_CHANGE_BATTLE_SWITCH: + if (IsGigantamaxed(battler)) + return TRUE; + else if (GetActiveGimmick(battler) == GIMMICK_TERA && GetBattlerAbility(battler) == ABILITY_HUNGER_SWITCH) + return FALSE; + break; + default: + break; + } return DoesSpeciesHaveFormChangeMethod(gBattleMons[battler].species, method); } diff --git a/test/battle/ability/hunger_switch.c b/test/battle/ability/hunger_switch.c index 1892de4424..469cf02d3c 100644 --- a/test/battle/ability/hunger_switch.c +++ b/test/battle/ability/hunger_switch.c @@ -53,7 +53,6 @@ SINGLE_BATTLE_TEST("Hunger Switch does not switch Morpeko's form when Terastalli SINGLE_BATTLE_TEST("Hunger Switch does not switch Morpeko's form after switching out while Terastallized") { - KNOWN_FAILING; // #7062 GIVEN { ASSUME(GetMoveEffect(MOVE_ROAR) == EFFECT_ROAR); PLAYER(SPECIES_MORPEKO) { Ability(ABILITY_HUNGER_SWITCH); TeraType(TYPE_NORMAL); } From c77c624e9f53f1b2abf7c044b3b59d33a5be6290 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:14:03 +0100 Subject: [PATCH 06/12] Fixes Gooey/Tangling Hair ability pop up triggering on Clear Body (#8083) --- data/battle_scripts_1.s | 2 ++ test/battle/ability/tangling_hair.c | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index d65e846880..de2148bab2 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -8214,11 +8214,13 @@ BattleScript_CuteCharmActivates:: return BattleScript_GooeyActivates:: + statbuffchange BS_ATTACKER, STAT_CHANGE_ONLY_CHECKING, BattleScript_GooeyActivatesRet waitstate call BattleScript_AbilityPopUp swapattackerwithtarget @ for defiant, mirror armor seteffectsecondary BS_ATTACKER, BS_TARGET, MOVE_EFFECT_SPD_MINUS_1 swapattackerwithtarget +BattleScript_GooeyActivatesRet: return BattleScript_AbilityStatusEffect:: diff --git a/test/battle/ability/tangling_hair.c b/test/battle/ability/tangling_hair.c index 13a0f27cb0..849c83df64 100644 --- a/test/battle/ability/tangling_hair.c +++ b/test/battle/ability/tangling_hair.c @@ -8,7 +8,6 @@ ASSUMPTIONS ASSUME(MoveMakesContact(MOVE_SCRATCH) == TRUE); } - SINGLE_BATTLE_TEST("Tangling Hair drops opposing mon's speed if ability user got hit by a contact move") { u32 move; @@ -85,3 +84,16 @@ SINGLE_BATTLE_TEST("Tangling Hair does not activate on confusion damage") } } } + +SINGLE_BATTLE_TEST("Tangling Hair does not trigger on Clear Body") +{ + GIVEN { + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_TANGLING_HAIR); } + OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + NOT ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); + } +} From 8de86ba243ac41802995e1bf16281155ac4a9370 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Thu, 30 Oct 2025 13:14:07 -0300 Subject: [PATCH 07/12] Fixed Belly Drum/Contrary interaction at max Attack (#8078) --- asm/macros/battle_script.inc | 7 ++- data/battle_scripts_1.s | 2 +- include/battle_util.h | 2 +- src/battle_script_commands.c | 50 ++++++++++++------- src/battle_util.c | 72 ++++++++++++++-------------- src/item_use.c | 7 ++- test/battle/move_effect/belly_drum.c | 4 +- 7 files changed, 84 insertions(+), 60 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 5ccaf2ea50..6203c3a7bf 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -187,8 +187,13 @@ .4byte \jumpInstr .endm - .macro unused_0x21 + .macro jumpifstatignorecontrary battler:req, comparison:req, stat:req, value:req, jumpInstr:req .byte 0x21 + .byte \battler + .byte \comparison + .byte \stat + .byte \value + .4byte \jumpInstr .endm .macro jumpbasedontype battler:req, type:req, jumpIfType:req, jumpInstr:req diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index de2148bab2..49768b2c43 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -4221,7 +4221,7 @@ BattleScript_EffectBellyDrum:: attackcanceler attackstring ppreduce - jumpifstat BS_ATTACKER, CMP_EQUAL, STAT_ATK, MAX_STAT_STAGE, BattleScript_ButItFailed + jumpifstatignorecontrary BS_ATTACKER, CMP_EQUAL, STAT_ATK, MAX_STAT_STAGE, BattleScript_ButItFailed halvehp BattleScript_ButItFailed orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_PASSIVE_HP_UPDATE attackanimation diff --git a/include/battle_util.h b/include/battle_util.h index 8601c792f6..dccd31d503 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -332,7 +332,7 @@ bool32 IsPartnerMonFromSameTrainer(u32 battler); enum ItemEffect TryHandleSeed(u32 battler, u32 terrainFlag, u32 statId, u32 itemId, enum ItemCaseId caseID); bool32 IsBattlerAffectedByHazards(u32 battler, bool32 toxicSpikes); void SortBattlersBySpeed(u8 *battlers, bool32 slowToFast); -bool32 CompareStat(u32 battler, u8 statId, u8 cmpTo, u8 cmpKind); +bool32 CompareStat(u32 battler, u8 statId, u8 cmpTo, u8 cmpKind, u32 ability); bool32 TryRoomService(u32 battler); void BufferStatChange(u32 battler, u8 statId, enum StringID stringId); bool32 BlocksPrankster(u16 move, u32 battlerPrankster, u32 battlerDef, bool32 checkTarget); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 6d9ba3d61e..e58b9c57e6 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -370,7 +370,7 @@ static void Cmd_jumpifvolatile(void); static void Cmd_jumpifability(void); static void Cmd_jumpifsideaffecting(void); static void Cmd_jumpifstat(void); -static void Cmd_unused_0x21(void); +static void Cmd_jumpifstatignorecontrary(void); static void Cmd_jumpbasedontype(void); static void Cmd_getexp(void); static void Cmd_checkteamslost(void); @@ -629,7 +629,7 @@ void (*const gBattleScriptingCommandsTable[])(void) = Cmd_jumpifability, //0x1E Cmd_jumpifsideaffecting, //0x1F Cmd_jumpifstat, //0x20 - Cmd_unused_0x21, //0x21 + Cmd_jumpifstatignorecontrary, //0x21 Cmd_jumpbasedontype, //0x22 Cmd_getexp, //0x23 Cmd_checkteamslost, //0x24 @@ -4432,7 +4432,7 @@ static void Cmd_jumpifstat(void) u8 value = cmd->value; u8 comparison = cmd->comparison; - ret = CompareStat(battler, stat, value, comparison); + ret = CompareStat(battler, stat, value, comparison, GetBattlerAbility(battler)); if (ret) gBattlescriptCurrInstr = cmd->jumpInstr; @@ -4440,8 +4440,22 @@ static void Cmd_jumpifstat(void) gBattlescriptCurrInstr = cmd->nextInstr; } -static void Cmd_unused_0x21(void) +static void Cmd_jumpifstatignorecontrary(void) { + CMD_ARGS(u8 battler, u8 comparison, u8 stat, u8 value, const u8 *jumpInstr); + + bool32 ret = 0; + u8 battler = GetBattlerForBattleScript(cmd->battler); + u8 stat = cmd->stat; + u8 value = cmd->value; + u8 comparison = cmd->comparison; + + ret = CompareStat(battler, stat, value, comparison, ABILITY_NONE); + + if (ret) + gBattlescriptCurrInstr = cmd->jumpInstr; + else + gBattlescriptCurrInstr = cmd->nextInstr; } static void Cmd_jumpbasedontype(void) @@ -5620,7 +5634,7 @@ static bool32 HandleMoveEndAbilityBlock(u32 battlerAtk, u32 battlerDef, u32 move else if (abilityAtk == ABILITY_GRIM_NEIGH || abilityAtk == ABILITY_AS_ONE_SHADOW_RIDER) stat = STAT_SPATK; - if (numMonsFainted && CompareStat(battlerAtk, stat, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (numMonsFainted && CompareStat(battlerAtk, stat, MAX_STAT_STAGE, CMP_LESS_THAN, abilityAtk)) { gLastUsedAbility = abilityAtk; if (abilityAtk == ABILITY_AS_ONE_ICE_RIDER) @@ -5660,17 +5674,17 @@ static bool32 HandleMoveEndAbilityBlock(u32 battlerAtk, u32 battlerDef, u32 move else { u32 numStatBuffs = 0; - if (CompareStat(battlerAtk, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (CompareStat(battlerAtk, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, abilityAtk)) { gBattleScripting.animArg1 = GET_STAT_BUFF_ID(STAT_ATK) + STAT_ANIM_PLUS1; numStatBuffs++; } - if (CompareStat(battlerAtk, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (CompareStat(battlerAtk, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN, abilityAtk)) { gBattleScripting.animArg1 = GET_STAT_BUFF_ID(STAT_SPATK) + STAT_ANIM_PLUS1; numStatBuffs++; } - if (CompareStat(battlerAtk, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (CompareStat(battlerAtk, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, abilityAtk)) { gBattleScripting.animArg1 = GET_STAT_BUFF_ID(STAT_SPEED) + STAT_ANIM_PLUS1; numStatBuffs++; @@ -5876,7 +5890,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) && !IsBattlerAlive(gBattlerTarget) && IsBattlerTurnDamaged(gBattlerTarget) && !NoAliveMonsForEitherParty() - && CompareStat(gBattlerAttacker, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + && CompareStat(gBattlerAttacker, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerAttacker))) { SET_STATCHANGER(STAT_ATK, GetGenConfig(GEN_CONFIG_FELL_STINGER_STAT_RAISE) >= GEN_7 ? 3 : 2, FALSE); PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_ATK); @@ -6106,7 +6120,7 @@ static void Cmd_moveend(void) && !IsBattlerAlly(gBattlerAttacker, gBattlerTarget) && IsBattlerTurnDamaged(gBattlerTarget) && !IsBattleMoveStatus(gCurrentMove) - && CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + && CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerTarget))) { SET_STATCHANGER(STAT_ATK, 1, FALSE); BattleScriptCall(BattleScript_RageIsBuilding); @@ -6522,7 +6536,7 @@ static void Cmd_moveend(void) && GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_THROAT_SPRAY && IsBattlerAlive(gBattlerAttacker) && IsAnyTargetAffected(gBattlerAttacker) - && CompareStat(gBattlerAttacker, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN) + && CompareStat(gBattlerAttacker, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerAttacker)) && !NoAliveMonsForEitherParty()) // Don't activate if battle will end { gLastUsedItem = gBattleMons[gBattlerAttacker].item; @@ -12089,7 +12103,7 @@ static void Cmd_jumpifconfusedandstatmaxed(void) CMD_ARGS(u8 stat, const u8 *jumpInstr); if (gBattleMons[gBattlerTarget].volatiles.confusionTurns > 0 - && !CompareStat(gBattlerTarget, cmd->stat, MAX_STAT_STAGE, CMP_LESS_THAN)) + && !CompareStat(gBattlerTarget, cmd->stat, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerTarget))) gBattlescriptCurrInstr = cmd->jumpInstr; // Fails if we're confused AND stat cannot be raised else gBattlescriptCurrInstr = cmd->nextInstr; @@ -16014,7 +16028,7 @@ void BS_CanTarShotWork(void) NATIVE_ARGS(const u8 *failInstr); // Tar Shot will fail if it's already been used on the target or if its speed can't be lowered further if (!gDisableStructs[gBattlerTarget].tarShot - && CompareStat(gBattlerTarget, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN)) + && CompareStat(gBattlerTarget, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerTarget))) gBattlescriptCurrInstr = cmd->nextInstr; else gBattlescriptCurrInstr = cmd->failInstr; @@ -16968,7 +16982,7 @@ void BS_TryAcupressure(void) u32 bits = 0; for (u32 stat = STAT_ATK; stat < NUM_BATTLE_STATS; stat++) { - if (CompareStat(gBattlerTarget, stat, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (CompareStat(gBattlerTarget, stat, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerTarget))) bits |= 1u << stat; } if (bits) @@ -17367,10 +17381,11 @@ void BS_TryActivateSoulheart(void) while (gBattleStruct->soulheartBattlerId < gBattlersCount) { gBattleScripting.battler = gBattleStruct->soulheartBattlerId++; - if (GetBattlerAbility(gBattleScripting.battler) == ABILITY_SOUL_HEART + u32 ability = GetBattlerAbility(gBattleScripting.battler); + if (ability == ABILITY_SOUL_HEART && IsBattlerAlive(gBattleScripting.battler) && !NoAliveMonsForEitherParty() - && CompareStat(gBattleScripting.battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + && CompareStat(gBattleScripting.battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN, ability)) { SET_STATCHANGER(STAT_SPATK, 1, FALSE); PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_SPATK); @@ -18278,10 +18293,11 @@ void BS_CutOneThirdHpAndRaiseStats(void) bool8 atLeastOneStatBoosted = FALSE; u16 hpFraction = max(1, GetNonDynamaxMaxHP(gBattlerAttacker) / 3); + u32 ability = GetBattlerAbility(gBattlerAttacker); for (u32 stat = 1; stat < NUM_STATS; stat++) { - if (CompareStat(gBattlerAttacker, stat, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (CompareStat(gBattlerAttacker, stat, MAX_STAT_STAGE, CMP_LESS_THAN, ability)) { atLeastOneStatBoosted = TRUE; break; diff --git a/src/battle_util.c b/src/battle_util.c index 182910aadc..c35a1f00a3 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -1196,7 +1196,7 @@ void PrepareStringBattle(enum StringID stringId, u32 battler) else if (GetGenConfig(GEN_CONFIG_UPDATED_INTIMIDATE) >= GEN_8 && stringId == STRINGID_PKMNCUTSATTACKWITH && targetAbility == ABILITY_RATTLED - && CompareStat(gBattlerTarget, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN)) + && CompareStat(gBattlerTarget, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, targetAbility)) { gBattlerAbility = gBattlerTarget; BattleScriptCall(BattleScript_AbilityRaisesDefenderStat); @@ -1702,13 +1702,13 @@ u32 GetBattlerAffectionHearts(u32 battler) // gBattlerAttacker is the battler that's trying to raise their stats and due to limitations of RandomUniformExcept, cannot be an argument bool32 MoodyCantRaiseStat(u32 stat) { - return CompareStat(gBattlerAttacker, stat, MAX_STAT_STAGE, CMP_EQUAL); + return CompareStat(gBattlerAttacker, stat, MAX_STAT_STAGE, CMP_EQUAL, GetBattlerAbility(gBattlerAttacker)); } // gBattlerAttacker is the battler that's trying to lower their stats and due to limitations of RandomUniformExcept, cannot be an argument bool32 MoodyCantLowerStat(u32 stat) { - return stat == GET_STAT_BUFF_ID(gBattleScripting.statChanger) || CompareStat(gBattlerAttacker, stat, MIN_STAT_STAGE, CMP_EQUAL); + return stat == GET_STAT_BUFF_ID(gBattleScripting.statChanger) || CompareStat(gBattlerAttacker, stat, MIN_STAT_STAGE, CMP_EQUAL, GetBattlerAbility(gBattlerAttacker)); } void TryToRevertMimicryAndFlags(void) @@ -3213,7 +3213,7 @@ bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 break; case MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY: gBattleStruct->pledgeMove = FALSE; - if (!CompareStat(battlerDef, statId, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (!CompareStat(battlerDef, statId, MAX_STAT_STAGE, CMP_LESS_THAN, abilityDef)) { if ((gProtectStructs[battlerAtk].notFirstStrike)) battleScript = BattleScript_MonMadeMoveUseless; @@ -3776,7 +3776,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 gSpecialStatuses[battler].switchInAbilityDone = TRUE; - if (CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) { SET_STATCHANGER(statId, 1, FALSE); SaveBattlerAttacker(gBattlerAttacker); @@ -3988,7 +3988,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (GetGenConfig(GEN_INTREPID_SWORD) == GEN_9) GetBattlerPartyState(battler)->intrepidSwordBoost = TRUE; gSpecialStatuses[battler].switchInAbilityDone = TRUE; - if (CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) { SET_STATCHANGER(STAT_ATK, 1, FALSE); BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn); @@ -4003,7 +4003,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (GetGenConfig(GEN_DAUNTLESS_SHIELD) == GEN_9) GetBattlerPartyState(battler)->dauntlessShieldBoost = TRUE; gSpecialStatuses[battler].switchInAbilityDone = TRUE; - if (CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) { SET_STATCHANGER(STAT_DEF, 1, FALSE); BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn); @@ -4013,7 +4013,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 break; case ABILITY_WIND_RIDER: if (!gSpecialStatuses[battler].switchInAbilityDone - && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN) + && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) && gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_TAILWIND) { gSpecialStatuses[battler].switchInAbilityDone = TRUE; @@ -4149,7 +4149,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 else //ABILITY_EMBODY_ASPECT_TEAL_MASK stat = STAT_SPEED; - if (CompareStat(battler, stat, MAX_STAT_STAGE, CMP_EQUAL)) + if (CompareStat(battler, stat, MAX_STAT_STAGE, CMP_EQUAL, gLastUsedAbility)) break; gSpecialStatuses[battler].switchInAbilityDone = TRUE; @@ -4302,7 +4302,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_SPEED_BOOST: - if (CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN) && gDisableStructs[battler].isFirstTurn != 2) + if (CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) && gDisableStructs[battler].isFirstTurn != 2) { SET_STATCHANGER(STAT_SPEED, 1, FALSE); BattleScriptPushCursorAndCallback(BattleScript_SpeedBoostActivates); @@ -4318,9 +4318,9 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 for (i = STAT_ATK; i < statsNum; i++) { - if (CompareStat(battler, i, MIN_STAT_STAGE, CMP_GREATER_THAN)) + if (CompareStat(battler, i, MIN_STAT_STAGE, CMP_GREATER_THAN, gLastUsedAbility)) validToLower |= 1u << i; - if (CompareStat(battler, i, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (CompareStat(battler, i, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) validToRaise |= 1u << i; } @@ -4457,7 +4457,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (IsBattlerTurnDamaged(battler) && IsBattlerAlive(battler) && moveType == TYPE_DARK - && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) { gEffectBattler = battler; SET_STATCHANGER(STAT_ATK, 1, FALSE); @@ -4469,7 +4469,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (IsBattlerTurnDamaged(battler) && IsBattlerAlive(battler) && (moveType == TYPE_DARK || moveType == TYPE_BUG || moveType == TYPE_GHOST) - && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN)) + && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) { gEffectBattler = battler; SET_STATCHANGER(STAT_SPEED, 1, FALSE); @@ -4481,7 +4481,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (IsBattlerTurnDamaged(battler) && IsBattlerAlive(battler) && moveType == TYPE_WATER - && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN)) + && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) { gEffectBattler = battler; SET_STATCHANGER(STAT_DEF, 2, FALSE); @@ -4493,7 +4493,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (gBattlerAttacker != gBattlerTarget && IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(battler) - && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN)) + && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) { gEffectBattler = battler; SET_STATCHANGER(STAT_DEF, 1, FALSE); @@ -4507,7 +4507,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && HadMoreThanHalfHpNowDoesnt(battler) && (gMultiHitCounter == 0 || gMultiHitCounter == 1) && !(TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)) - && CompareStat(battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + && CompareStat(battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) { gEffectBattler = battler; SET_STATCHANGER(STAT_SPATK, 1, FALSE); @@ -4519,8 +4519,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (IsBattlerTurnDamaged(battler) && IsBattlerAlive(battler) && IsBattleMovePhysical(gCurrentMove) - && (CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN) // Don't activate if both Speed and Defense cannot be raised. - || CompareStat(battler, STAT_DEF, MIN_STAT_STAGE, CMP_GREATER_THAN))) + && (CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) // Don't activate if both Speed and Defense cannot be raised. + || CompareStat(battler, STAT_DEF, MIN_STAT_STAGE, CMP_GREATER_THAN, gLastUsedAbility))) { if (GetMoveEffect(gCurrentMove) == EFFECT_HIT_ESCAPE && CanBattlerSwitch(gBattlerAttacker)) gProtectStructs[battler].disableEjectPack = TRUE; // Set flag for target @@ -4597,7 +4597,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (gSpecialStatuses[battler].criticalHit && IsBattlerTurnDamaged(battler) && IsBattlerAlive(battler) - && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) { SET_STATCHANGER(STAT_ATK, MAX_STAT_STAGE - gBattleMons[battler].statStages[STAT_ATK], FALSE); BattleScriptCall(BattleScript_TargetsStatWasMaxedOut); @@ -4622,7 +4622,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_GOOEY: case ABILITY_TANGLING_HAIR: if (IsBattlerAlive(gBattlerAttacker) - && (CompareStat(gBattlerAttacker, STAT_SPEED, MIN_STAT_STAGE, CMP_GREATER_THAN) || GetBattlerAbility(gBattlerAttacker) == ABILITY_MIRROR_ARMOR) + && (CompareStat(gBattlerAttacker, STAT_SPEED, MIN_STAT_STAGE, CMP_GREATER_THAN, gLastUsedAbility) || GetBattlerAbility(gBattlerAttacker) == ABILITY_MIRROR_ARMOR) && !gProtectStructs[gBattlerAttacker].confusionSelfDmg && IsBattlerTurnDamaged(gBattlerTarget) && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), move)) @@ -4830,7 +4830,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_STEAM_ENGINE: if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(battler) - && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN) + && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) && (moveType == TYPE_FIRE || moveType == TYPE_WATER)) { gEffectBattler = battler; @@ -4916,7 +4916,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_THERMAL_EXCHANGE: if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerTarget) - && CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN) + && CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) && moveType == TYPE_FIRE) { gEffectBattler = gBattlerTarget; @@ -5870,11 +5870,12 @@ static enum ItemEffect HealConfuseBerry(u32 battler, u32 itemId, u32 flavorId, e static enum ItemEffect StatRaiseBerry(u32 battler, u32 itemId, u32 statId, enum ItemCaseId caseID) { - if (CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN) && HasEnoughHpToEatBerry(battler, GetBattlerItemHoldEffectParam(battler, itemId), itemId)) + u32 ability = GetBattlerAbility(battler); + if (CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN, ability) && HasEnoughHpToEatBerry(battler, GetBattlerItemHoldEffectParam(battler, itemId), itemId)) { BufferStatChange(battler, statId, STRINGID_STATROSE); gEffectBattler = gBattleScripting.battler = battler; - if (GetBattlerAbility(battler) == ABILITY_RIPEN) + if (ability == ABILITY_RIPEN) SET_STATCHANGER(statId, 2, FALSE); else SET_STATCHANGER(statId, 1, FALSE); @@ -5895,15 +5896,15 @@ static enum ItemEffect RandomStatRaiseBerry(u32 battler, u32 itemId, enum ItemCa { s32 stat; enum StringID stringId; + u32 battlerAbility = GetBattlerAbility(battler); for (stat = STAT_ATK; stat < NUM_STATS; stat++) { - if (CompareStat(battler, stat, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (CompareStat(battler, stat, MAX_STAT_STAGE, CMP_LESS_THAN, battlerAbility)) break; } if (stat != NUM_STATS && HasEnoughHpToEatBerry(battler, GetBattlerItemHoldEffectParam(battler, itemId), itemId)) { - u16 battlerAbility = GetBattlerAbility(battler); u32 savedAttacker = gBattlerAttacker; // MoodyCantRaiseStat requires that the battler is set to gBattlerAttacker gBattlerAttacker = gBattleScripting.battler = battler; @@ -5976,8 +5977,9 @@ static enum ItemEffect TrySetEnigmaBerry(u32 battler) static enum ItemEffect DamagedStatBoostBerryEffect(u32 battler, u8 statId, enum DamageCategory category) { + u32 ability = GetBattlerAbility(battler); if (IsBattlerAlive(battler) - && CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN) + && CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN, ability) && (gBattleScripting.overrideBerryRequirements || (!DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove) && GetBattleMoveCategory(gCurrentMove) == category @@ -5988,7 +5990,7 @@ static enum ItemEffect DamagedStatBoostBerryEffect(u32 battler, u8 statId, enum BufferStatChange(battler, statId, STRINGID_STATROSE); gEffectBattler = battler; - if (GetBattlerAbility(battler) == ABILITY_RIPEN) + if (ability == ABILITY_RIPEN) SET_STATCHANGER(statId, 2, FALSE); else SET_STATCHANGER(statId, 1, FALSE); @@ -6004,7 +6006,7 @@ static enum ItemEffect DamagedStatBoostBerryEffect(u32 battler, u8 statId, enum enum ItemEffect TryHandleSeed(u32 battler, u32 terrainFlag, u32 statId, u32 itemId, enum ItemCaseId caseID) { - if (gFieldStatuses & terrainFlag && CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (gFieldStatuses & terrainFlag && CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(battler))) { BufferStatChange(battler, statId, STRINGID_STATROSE); gLastUsedItem = itemId; // For surge abilities @@ -6961,7 +6963,7 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler) case HOLD_EFFECT_BLUNDER_POLICY: if (gBattleStruct->blunderPolicy && IsBattlerAlive(gBattlerAttacker) - && CompareStat(gBattlerAttacker, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN)) + && CompareStat(gBattlerAttacker, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerAttacker))) { gBattleStruct->blunderPolicy = FALSE; gLastUsedItem = atkItem; @@ -10757,15 +10759,15 @@ bool32 TestIfSheerForceAffected(u32 battler, u16 move) return GetBattlerAbility(battler) == ABILITY_SHEER_FORCE && MoveIsAffectedBySheerForce(move); } -// This function is the body of "jumpifstat", but can be used dynamically in a function -bool32 CompareStat(u32 battler, u8 statId, u8 cmpTo, u8 cmpKind) +// This function is the body of "jumpifstat", but can be used dynamically in a function. It considers Contrary. +bool32 CompareStat(u32 battler, u8 statId, u8 cmpTo, u8 cmpKind, u32 ability) { bool32 ret = FALSE; u8 statValue = gBattleMons[battler].statStages[statId]; // Because this command is used as a way of checking if a stat can be lowered/raised, // we need to do some modification at run-time. - if (GetBattlerAbility(battler) == ABILITY_CONTRARY) + if (ability == ABILITY_CONTRARY) { if (cmpKind == CMP_GREATER_THAN) cmpKind = CMP_LESS_THAN; @@ -10836,7 +10838,7 @@ void BufferStatChange(u32 battler, u8 statId, enum StringID stringId) bool32 TryRoomService(u32 battler) { - if (gFieldStatuses & STATUS_FIELD_TRICK_ROOM && CompareStat(battler, STAT_SPEED, MIN_STAT_STAGE, CMP_GREATER_THAN)) + if (gFieldStatuses & STATUS_FIELD_TRICK_ROOM && CompareStat(battler, STAT_SPEED, MIN_STAT_STAGE, CMP_GREATER_THAN, GetBattlerAbility(battler))) { BufferStatChange(battler, STAT_SPEED, STRINGID_STATFELL); gEffectBattler = gBattleScripting.battler = battler; diff --git a/src/item_use.c b/src/item_use.c index 0681575f45..b1baca20d2 100644 --- a/src/item_use.c +++ b/src/item_use.c @@ -128,7 +128,7 @@ static void SetUpItemUseCallback(u8 taskId) type = gTasks[taskId].tEnigmaBerryType - 1; else type = GetItemType(gSpecialVar_ItemId) - 1; - + if (gTasks[taskId].tUsingRegisteredKeyItem && type == (ITEM_USE_PARTY_MENU - 1)) { FadeScreen(FADE_TO_BLACK, 0); @@ -1291,15 +1291,18 @@ bool32 CannotUseItemsInBattle(u16 itemId, struct Pokemon *mon) } break; case EFFECT_ITEM_INCREASE_ALL_STATS: + { + u32 ability = GetBattlerAbility(gBattlerInMenuId); for (i = STAT_ATK; i < NUM_STATS; i++) { - if (CompareStat(gBattlerInMenuId, i, MAX_STAT_STAGE, CMP_EQUAL)) + if (CompareStat(gBattlerInMenuId, i, MAX_STAT_STAGE, CMP_EQUAL, ability)) { cannotUse = TRUE; break; } } break; + } case EFFECT_ITEM_RESTORE_HP: if (hp == 0 || hp == GetMonData(mon, MON_DATA_MAX_HP)) cannotUse = TRUE; diff --git a/test/battle/move_effect/belly_drum.c b/test/battle/move_effect/belly_drum.c index 20024f5b91..194ee5b9d0 100644 --- a/test/battle/move_effect/belly_drum.c +++ b/test/battle/move_effect/belly_drum.c @@ -159,7 +159,6 @@ SINGLE_BATTLE_TEST("Belly Drum maximizes the user's Attack stat, even when below SINGLE_BATTLE_TEST("Belly Drum fails if the user's Attack is already at +6, even with Contrary") { - KNOWN_FAILING; GIVEN { ASSUME(GetMoveEffect(MOVE_CHARM) == EFFECT_ATTACK_DOWN_2); PLAYER(SPECIES_SERPERIOR) { Ability(ABILITY_CONTRARY); } @@ -190,7 +189,6 @@ SINGLE_BATTLE_TEST("Belly Drum fails if the user's Attack is already at +6, even SINGLE_BATTLE_TEST("Belly Drum deducts HP if the user has Contrary and is at -6") { - KNOWN_FAILING; GIVEN { ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2); PLAYER(SPECIES_SERPERIOR) { Ability(ABILITY_CONTRARY); } @@ -215,6 +213,6 @@ SINGLE_BATTLE_TEST("Belly Drum deducts HP if the user has Contrary and is at -6" ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); HP_BAR(player, hp: maxHP / 2); - MESSAGE("Wobbuffet cut its own HP and maximized its Attack!"); + MESSAGE("Serperior cut its own HP and maximized its Attack!"); } } From 3a3b9476227d7aa16d7425dd8b07adef2f8efd7d Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Thu, 30 Oct 2025 17:51:57 +0100 Subject: [PATCH 08/12] Fixes intimidate activating on empty field (#8058) Co-authored-by: PhallenTree <168426989+PhallenTree@users.noreply.github.com> --- src/battle_util.c | 21 ++++++++++++++-- test/battle/ability/intimidate.c | 36 +++++++++++++++++----------- test/battle/move_effect/hit_escape.c | 6 ++--- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/battle_util.c b/src/battle_util.c index c35a1f00a3..a4c3fff748 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -67,6 +67,7 @@ static u32 GetBattlerItemHoldEffectParam(u32 battler, u32 item); static bool32 CanBeInfinitelyConfused(u32 battler); static bool32 IsNonVolatileStatusBlocked(u32 battlerDef, u32 abilityDef, u32 abilityAffected, const u8 *battleScript, enum FunctionCallOption option); static bool32 CanSleepDueToSleepClause(u32 battlerAtk, u32 battlerDef, enum FunctionCallOption option); +static bool32 IsOpposingSideEmpty(u32 battler); ARM_FUNC NOINLINE static uq4_12_t PercentToUQ4_12(u32 percent); ARM_FUNC NOINLINE static uq4_12_t PercentToUQ4_12_Floored(u32 percent); @@ -3929,7 +3930,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_INTIMIDATE: - if (!gSpecialStatuses[battler].switchInAbilityDone) + if (!gSpecialStatuses[battler].switchInAbilityDone && !IsOpposingSideEmpty(battler)) { SaveBattlerAttacker(gBattlerAttacker); gBattlerAttacker = battler; @@ -3941,7 +3942,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 break; case ABILITY_SUPERSWEET_SYRUP: if (!gSpecialStatuses[battler].switchInAbilityDone - && !GetBattlerPartyState(battler)->supersweetSyrup) + && !GetBattlerPartyState(battler)->supersweetSyrup + && !IsOpposingSideEmpty(battler)) { SaveBattlerAttacker(gBattlerAttacker); gBattlerAttacker = battler; @@ -11930,3 +11932,18 @@ bool32 HasPartnerTrainer(u32 battler) else return FALSE; } + +static bool32 IsOpposingSideEmpty(u32 battler) +{ + u32 oppositeBattler = BATTLE_OPPOSITE(battler); + + if (IsBattlerAlive(oppositeBattler)) + return FALSE; + + if (!IsDoubleBattle()) + return TRUE; + + if (IsBattlerAlive(BATTLE_PARTNER(oppositeBattler))) + return FALSE; + return TRUE; +} diff --git a/test/battle/ability/intimidate.c b/test/battle/ability/intimidate.c index c7308bfdaf..b0e4b19a19 100644 --- a/test/battle/ability/intimidate.c +++ b/test/battle/ability/intimidate.c @@ -240,6 +240,7 @@ DOUBLE_BATTLE_TEST("Intimidate is not going to trigger if a mon switches out thr ANIMATION(ANIM_TYPE_MOVE, MOVE_HEALING_WISH, opponentRight); ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, playerLeft); HP_BAR(opponentLeft); + NOT ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); MESSAGE("2 sent out Treecko!"); MESSAGE("2 sent out Torchic!"); NOT ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); @@ -264,7 +265,7 @@ SINGLE_BATTLE_TEST("Intimidate activates when it's no longer effected by Neutral } } -SINGLE_BATTLE_TEST("Intimidate activates when it's no longer affected by Neutralizing Gas - switching moves") +DOUBLE_BATTLE_TEST("Intimidate activates when it's no longer affected by Neutralizing Gas - switching moves") { u32 move; PARAMETRIZE { move = MOVE_U_TURN; } @@ -276,19 +277,24 @@ SINGLE_BATTLE_TEST("Intimidate activates when it's no longer affected by Neutral ASSUME(GetMoveEffect(MOVE_BATON_PASS) == EFFECT_BATON_PASS); PLAYER(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); } PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, move); SEND_OUT(player, 1); } + TURN { + if (move == MOVE_U_TURN) + MOVE(playerLeft, move, target: opponentLeft); + else + MOVE(playerLeft, move); + SEND_OUT(playerLeft, 2); + } } SCENE { - ABILITY_POPUP(player, ABILITY_NEUTRALIZING_GAS); + ABILITY_POPUP(playerLeft, ABILITY_NEUTRALIZING_GAS); MESSAGE("Neutralizing gas filled the area!"); - ANIMATION(ANIM_TYPE_MOVE, move, player); + ANIMATION(ANIM_TYPE_MOVE, move, playerLeft); MESSAGE("The effects of the neutralizing gas wore off!"); - ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); SEND_IN_MESSAGE("Wobbuffet"); - } THEN { - if (move == MOVE_HEALING_WISH) - EXPECT_EQ(player->hp, player->maxHP); } } @@ -330,23 +336,25 @@ SINGLE_BATTLE_TEST("Intimidate activates when it's no longer affected by Neutral } } -SINGLE_BATTLE_TEST("Intimidate activates when it's no longer affected by Neutralizing Gas - fainted") +DOUBLE_BATTLE_TEST("Intimidate activates when it's no longer affected by Neutralizing Gas - fainted") { GIVEN { ASSUME(GetMoveEffect(MOVE_FELL_STINGER) == EFFECT_FELL_STINGER); PLAYER(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); HP(1); } PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_FELL_STINGER); SEND_OUT(player, 1); } + TURN { MOVE(opponentLeft, MOVE_FELL_STINGER, target: playerLeft); SEND_OUT(playerLeft, 2); } } SCENE { - ABILITY_POPUP(player, ABILITY_NEUTRALIZING_GAS); + ABILITY_POPUP(playerLeft, ABILITY_NEUTRALIZING_GAS); MESSAGE("Neutralizing gas filled the area!"); - ANIMATION(ANIM_TYPE_MOVE, MOVE_FELL_STINGER, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FELL_STINGER, opponentLeft); MESSAGE("The effects of the neutralizing gas wore off!"); - ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); MESSAGE("Weezing fainted!"); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); SEND_IN_MESSAGE("Wobbuffet"); } } diff --git a/test/battle/move_effect/hit_escape.c b/test/battle/move_effect/hit_escape.c index 0ced64d7e5..f587d078a0 100644 --- a/test/battle/move_effect/hit_escape.c +++ b/test/battle/move_effect/hit_escape.c @@ -113,7 +113,7 @@ SINGLE_BATTLE_TEST("Hit Escape: U-turn switches the user out after Ice Face acti } } -SINGLE_BATTLE_TEST("Hit Escape: Held items are consumed immediately after a mon switched in by U-turn and Intimidate activates after it: player side") +SINGLE_BATTLE_TEST("Hit Escape: Held items are consumed immediately after a mon switched in by U-turn: player side") { GIVEN { PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; @@ -126,7 +126,6 @@ SINGLE_BATTLE_TEST("Hit Escape: Held items are consumed immediately after a mon ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); HP_BAR(opponent); - ABILITY_POPUP(player, ABILITY_INTIMIDATE); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("2 sent out Wynaut!"); @@ -136,7 +135,7 @@ SINGLE_BATTLE_TEST("Hit Escape: Held items are consumed immediately after a mon } } -SINGLE_BATTLE_TEST("Hit Escape: Held items are consumed immediately after a mon switched in by U-turn and Intimidate activates after it: opposing side") +SINGLE_BATTLE_TEST("Hit Escape: Held items are consumed immediately after a mon switched in by U-turn: opposing side") { GIVEN { PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; @@ -149,7 +148,6 @@ SINGLE_BATTLE_TEST("Hit Escape: Held items are consumed immediately after a mon ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); HP_BAR(opponent); - ABILITY_POPUP(player, ABILITY_INTIMIDATE); MESSAGE("2 sent out Wynaut!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); From 0b005d4b6f043ec7a4307d796cfda8fbb513d003 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Thu, 30 Oct 2025 19:47:22 +0100 Subject: [PATCH 09/12] Fix non-battle trainer script not running properly (#8056) --- src/trainer_see.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/trainer_see.c b/src/trainer_see.c index 4439ce6977..54ea4ebf22 100644 --- a/src/trainer_see.c +++ b/src/trainer_see.c @@ -380,6 +380,7 @@ bool8 CheckForTrainersWantingBattle(void) if (numTrainers == 0xFF) // non-trainerbatle script { u32 objectEventId = gApproachingTrainers[gNoOfApproachingTrainers - 1].objectEventId; + gApproachingTrainers[gNoOfApproachingTrainers - 1].trainerScriptPtr = GetObjectEventScriptPointerByObjectEventId(objectEventId); gSelectedObjectEvent = objectEventId; gSpecialVar_LastTalked = gObjectEvents[objectEventId].localId; ScriptContext_SetupScript(EventScript_ObjectApproachPlayer); From 79441c65749065618caa68eda5f68e56cb631fbc Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Fri, 31 Oct 2025 17:44:08 +0100 Subject: [PATCH 10/12] Fix bug where mon selection doesn't properly account for party order (#8088) --- src/party_menu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/party_menu.c b/src/party_menu.c index 8a9532eaea..891066dd41 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -1511,7 +1511,7 @@ static void HandleChooseMonSelection(u8 taskId, s8 *slotPtr) } case PARTY_ACTION_SEND_MON_TO_BOX: { - u8 partyId = GetPartyIdFromBattleSlot((u8)*slotPtr); + u8 partyId = (u8)*slotPtr; if (partyId == 0 || ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && partyId == 1)) { // Can't select if mon is currently on the field @@ -1538,7 +1538,7 @@ static void HandleChooseMonSelection(u8 taskId, s8 *slotPtr) else { PlaySE(SE_SELECT); - gSelectedMonPartyId = partyId; + gSelectedMonPartyId = GetPartyIdFromBattleSlot(partyId); Task_ClosePartyMenu(taskId); } break; From 6c3f87e74ef63bd044f907eecb35f7f138c8f708 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Fri, 31 Oct 2025 17:50:29 +0100 Subject: [PATCH 11/12] Fix bug when a captured pokemon replaces a party member who changed forms (#8091) --- include/battle_util.h | 1 + src/battle_main.c | 12 +----------- src/battle_script_commands.c | 2 ++ src/battle_util.c | 17 +++++++++++++++++ 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/include/battle_util.h b/include/battle_util.h index dccd31d503..7bb8bc7292 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -300,6 +300,7 @@ bool32 IsBattlerMegaEvolved(u32 battler); bool32 IsBattlerPrimalReverted(u32 battler); bool32 IsBattlerUltraBursted(u32 battler); u16 GetBattleFormChangeTargetSpecies(u32 battler, enum FormChanges method); +bool32 TryRevertPartyMonFormChange(u32 partyIndex); bool32 TryBattleFormChange(u32 battler, enum FormChanges method); bool32 DoBattlersShareType(u32 battler1, u32 battler2); bool32 CanBattlerGetOrLoseItem(u32 battler, u16 itemId); diff --git a/src/battle_main.c b/src/battle_main.c index 99464151a8..cb41e88c73 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -5627,17 +5627,7 @@ static void HandleEndTurn_FinishBattle(void) for (i = 0; i < PARTY_SIZE; i++) { - bool8 changedForm = FALSE; - - // Appeared in battle and didn't faint - if ((gBattleStruct->appearedInBattle & (1u << i)) && GetMonData(&gPlayerParty[i], MON_DATA_HP, NULL) != 0) - changedForm = TryFormChange(i, B_SIDE_PLAYER, FORM_CHANGE_END_BATTLE_ENVIRONMENT); - - if (!changedForm) - changedForm = TryFormChange(i, B_SIDE_PLAYER, FORM_CHANGE_END_BATTLE); - - // Clear original species field - gBattleStruct->partyState[B_SIDE_PLAYER][i].changedSpecies = SPECIES_NONE; + bool8 changedForm = TryRevertPartyMonFormChange(i); gBattleStruct->partyState[B_SIDE_OPPONENT][i].changedSpecies = SPECIES_NONE; // Recalculate the stats of every party member before the end diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index e58b9c57e6..353d0e16e7 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -14155,6 +14155,8 @@ static void Cmd_givecaughtmon(void) } else { + //Before sending to PC, we revert battle form + TryRevertPartyMonFormChange(gSelectedMonPartyId); // Mon chosen, try to put it in the PC if (CopyMonToPC(&gPlayerParty[gSelectedMonPartyId]) == MON_GIVEN_TO_PC) { diff --git a/src/battle_util.c b/src/battle_util.c index a4c3fff748..91299c64df 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -10131,6 +10131,23 @@ bool32 CanBattlerFormChange(u32 battler, enum FormChanges method) return DoesSpeciesHaveFormChangeMethod(gBattleMons[battler].species, method); } +bool32 TryRevertPartyMonFormChange(u32 partyIndex) +{ + bool32 changedForm = FALSE; + + // Appeared in battle and didn't faint + if ((gBattleStruct->appearedInBattle & (1u << partyIndex)) && GetMonData(&gPlayerParty[partyIndex], MON_DATA_HP, NULL) != 0) + changedForm = TryFormChange(partyIndex, B_SIDE_PLAYER, FORM_CHANGE_END_BATTLE_ENVIRONMENT); + + if (!changedForm) + changedForm = TryFormChange(partyIndex, B_SIDE_PLAYER, FORM_CHANGE_END_BATTLE); + + // Clear original species field + gBattleStruct->partyState[B_SIDE_PLAYER][partyIndex].changedSpecies = SPECIES_NONE; + + return changedForm; +} + bool32 TryBattleFormChange(u32 battler, enum FormChanges method) { u32 monId = gBattlerPartyIndexes[battler]; From f66ba68eee5b83e290a55d0155c2be59096b6d41 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Fri, 31 Oct 2025 15:41:46 -0300 Subject: [PATCH 12/12] Fixed Zygarde Complete disappearing upon catch (#8089) --- src/battle_util.c | 27 ++++++++------ test/battle/ability/power_construct.c | 53 +++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/battle_util.c b/src/battle_util.c index 91299c64df..9adfcc5016 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -10161,7 +10161,7 @@ bool32 TryBattleFormChange(u32 battler, enum FormChanges method) targetSpecies = GetBattleFormChangeTargetSpecies(battler, method); if (targetSpecies == currentSpecies) targetSpecies = GetFormChangeTargetSpecies(&party[monId], method, 0); - if (targetSpecies != currentSpecies) + if (targetSpecies != currentSpecies && targetSpecies != SPECIES_NONE) { // Saves the original species on the first form change. @@ -10178,17 +10178,22 @@ bool32 TryBattleFormChange(u32 battler, enum FormChanges method) { bool32 restoreSpecies = FALSE; - // Mega Evolved and Ultra Bursted Pokémon should always revert to normal upon fainting or ending the battle, so no need to add it to the form change tables. - if ((IsBattlerMegaEvolved(battler) || IsBattlerUltraBursted(battler) || IsBattlerInTeraForm(battler)) && (method == FORM_CHANGE_FAINT || method == FORM_CHANGE_END_BATTLE)) - restoreSpecies = TRUE; - - // Unlike Megas, Primal Reversion isn't canceled on fainting. - else if (IsBattlerPrimalReverted(battler) && (method == FORM_CHANGE_END_BATTLE)) - restoreSpecies = TRUE; - - // Gigantamax Pokemon have their forms reverted after fainting, switching, or ending the battle. - else if (IsGigantamaxed(battler) && (method == FORM_CHANGE_FAINT || method == FORM_CHANGE_BATTLE_SWITCH || method == FORM_CHANGE_END_BATTLE)) + switch (method) + { + case FORM_CHANGE_END_BATTLE: restoreSpecies = TRUE; + break; + case FORM_CHANGE_FAINT: + if (IsBattlerMegaEvolved(battler) || IsBattlerUltraBursted(battler) || IsBattlerInTeraForm(battler) || IsGigantamaxed(battler)) + restoreSpecies = TRUE; + break; + case FORM_CHANGE_BATTLE_SWITCH: + if (IsGigantamaxed(battler)) + restoreSpecies = TRUE; + break; + default: + break; + } if (restoreSpecies) { diff --git a/test/battle/ability/power_construct.c b/test/battle/ability/power_construct.c index 6a8ca9db5f..07d52c1767 100644 --- a/test/battle/ability/power_construct.c +++ b/test/battle/ability/power_construct.c @@ -2,3 +2,56 @@ #include "test/battle.h" TO_DO_BATTLE_TEST("TODO: Write Power Construct (Ability) test titles") + +SINGLE_BATTLE_TEST("Power Construct switches Zygarde's form when HP is below half") +{ + u16 baseSpecies; + PARAMETRIZE { baseSpecies = SPECIES_ZYGARDE_10_POWER_CONSTRUCT; } + PARAMETRIZE { baseSpecies = SPECIES_ZYGARDE_50_POWER_CONSTRUCT; } + + GIVEN { + PLAYER(baseSpecies) + { + Ability(ABILITY_POWER_CONSTRUCT); + HP((GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP) / 2) + 1); + } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + MESSAGE("You sense the presence of many!"); + ABILITY_POPUP(player, ABILITY_POWER_CONSTRUCT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_POWER_CONSTRUCT, player); + } THEN { + EXPECT_EQ(player->species, SPECIES_ZYGARDE_COMPLETE); + } +} + +WILD_BATTLE_TEST("Power Construct Zygarde reverts to its original form upon catching") +{ + u16 baseSpecies; + PARAMETRIZE { baseSpecies = SPECIES_ZYGARDE_10_POWER_CONSTRUCT; } + PARAMETRIZE { baseSpecies = SPECIES_ZYGARDE_50_POWER_CONSTRUCT; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(baseSpecies) + { + Ability(ABILITY_POWER_CONSTRUCT); + HP((GetMonData(&OPPONENT_PARTY[0], MON_DATA_MAX_HP) / 2) + 1); + } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { USE_ITEM(player, ITEM_MASTER_BALL); } + } SCENE { + // Turn 1 + MESSAGE("You sense the presence of many!"); + ABILITY_POPUP(opponent, ABILITY_POWER_CONSTRUCT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_POWER_CONSTRUCT, opponent); + + // Turn 2 + ANIMATION(ANIM_TYPE_SPECIAL, B_ANIM_BALL_THROW, player); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[1], MON_DATA_SPECIES), baseSpecies); + } +}