From c5bbaeab5d2469a12dd323021328f08c0d45f98b Mon Sep 17 00:00:00 2001 From: Martin Griffin Date: Sat, 8 Nov 2025 14:54:18 +0000 Subject: [PATCH 01/18] STATIC_ASSERT for sizeof(struct ListMenu) --- src/list_menu.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/list_menu.c b/src/list_menu.c index 8257712a0a..6929b20b42 100644 --- a/src/list_menu.c +++ b/src/list_menu.c @@ -13,6 +13,10 @@ #include "sound.h" #include "constants/songs.h" +// GF cast Task data to ListMenu in many places, which effectively puts +// an upper bound on sizeof(struct ListMenu). +STATIC_ASSERT(sizeof(struct ListMenu) <= sizeof(((struct Task *)NULL)->data), ListMenuTooLargeForTaskData); + // Cursors after this point are created using a sprite with their own task. // This allows them to have idle animations. Cursors prior to this are simply printed text. #define CURSOR_OBJECT_START CURSOR_RED_OUTLINE From c0569cd5297948e8f7bbbf3c24ab8fdc14a7b4c5 Mon Sep 17 00:00:00 2001 From: mitsunee Date: Wed, 19 Nov 2025 21:14:45 +0100 Subject: [PATCH 02/18] Standardize spelling of "synchronize" --- include/constants/battle.h | 2 +- src/battle_script_commands.c | 4 ++-- src/battle_util.c | 12 ++++++------ src/field_effect_helpers.c | 12 ++++++------ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/include/constants/battle.h b/include/constants/battle.h index f6b4836ba2..1712dc0730 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -188,7 +188,7 @@ enum BattlerId #define HITMARKER_NO_PPDEDUCT (1 << 11) #define HITMARKER_SWAP_ATTACKER_TARGET (1 << 12) #define HITMARKER_STATUS_ABILITY_EFFECT (1 << 13) -#define HITMARKER_SYNCHRONISE_EFFECT (1 << 14) +#define HITMARKER_SYNCHRONIZE_EFFECT (1 << 14) #define HITMARKER_RUN (1 << 15) #define HITMARKER_IGNORE_ON_AIR (1 << 16) #define HITMARKER_IGNORE_UNDERGROUND (1 << 17) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 65663f77d3..30f347a9d4 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2507,7 +2507,7 @@ void SetMoveEffect(bool8 primary, u8 certain) { u8 *synchronizeEffect = &gBattleStruct->synchronizeMoveEffect; *synchronizeEffect = gBattleCommunication[MOVE_EFFECT_BYTE]; - gHitMarker |= HITMARKER_SYNCHRONISE_EFFECT; + gHitMarker |= HITMARKER_SYNCHRONIZE_EFFECT; } return; } @@ -3626,7 +3626,7 @@ static void MoveValuesCleanUp(void) gBattleCommunication[MOVE_EFFECT_BYTE] = 0; gBattleCommunication[MISS_TYPE] = 0; gHitMarker &= ~HITMARKER_DESTINYBOND; - gHitMarker &= ~HITMARKER_SYNCHRONISE_EFFECT; + gHitMarker &= ~HITMARKER_SYNCHRONIZE_EFFECT; } static void Cmd_movevaluescleanup(void) diff --git a/src/battle_util.c b/src/battle_util.c index 96c5dbc6f2..f511a86764 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -651,7 +651,7 @@ void HandleAction_NothingIsFainted(void) gHitMarker &= ~(HITMARKER_DESTINYBOND | HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_ATTACKSTRING_PRINTED | HITMARKER_NO_PPDEDUCT | HITMARKER_STATUS_ABILITY_EFFECT | HITMARKER_IGNORE_ON_AIR | HITMARKER_IGNORE_UNDERGROUND | HITMARKER_IGNORE_UNDERWATER | HITMARKER_PASSIVE_HP_UPDATE - | HITMARKER_OBEYS | HITMARKER_WAKE_UP_CLEAR | HITMARKER_SYNCHRONISE_EFFECT + | HITMARKER_OBEYS | HITMARKER_WAKE_UP_CLEAR | HITMARKER_SYNCHRONIZE_EFFECT | HITMARKER_CHARGING | HITMARKER_NEVER_SET); } @@ -664,7 +664,7 @@ void HandleAction_ActionFinished(void) gHitMarker &= ~(HITMARKER_DESTINYBOND | HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_ATTACKSTRING_PRINTED | HITMARKER_NO_PPDEDUCT | HITMARKER_STATUS_ABILITY_EFFECT | HITMARKER_IGNORE_ON_AIR | HITMARKER_IGNORE_UNDERGROUND | HITMARKER_IGNORE_UNDERWATER | HITMARKER_PASSIVE_HP_UPDATE - | HITMARKER_OBEYS | HITMARKER_WAKE_UP_CLEAR | HITMARKER_SYNCHRONISE_EFFECT + | HITMARKER_OBEYS | HITMARKER_WAKE_UP_CLEAR | HITMARKER_SYNCHRONIZE_EFFECT | HITMARKER_CHARGING | HITMARKER_NEVER_SET); gCurrentMove = 0; @@ -2969,9 +2969,9 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u8 ability, u8 special, u16 moveA } break; case ABILITYEFFECT_SYNCHRONIZE: // 7 - if (gLastUsedAbility == ABILITY_SYNCHRONIZE && (gHitMarker & HITMARKER_SYNCHRONISE_EFFECT)) + if (gLastUsedAbility == ABILITY_SYNCHRONIZE && (gHitMarker & HITMARKER_SYNCHRONIZE_EFFECT)) { - gHitMarker &= ~HITMARKER_SYNCHRONISE_EFFECT; + gHitMarker &= ~HITMARKER_SYNCHRONIZE_EFFECT; gBattleStruct->synchronizeMoveEffect &= ~(MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_CERTAIN); if (gBattleStruct->synchronizeMoveEffect == MOVE_EFFECT_TOXIC) gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_POISON; @@ -2985,9 +2985,9 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u8 ability, u8 special, u16 moveA } break; case ABILITYEFFECT_ATK_SYNCHRONIZE: // 8 - if (gLastUsedAbility == ABILITY_SYNCHRONIZE && (gHitMarker & HITMARKER_SYNCHRONISE_EFFECT)) + if (gLastUsedAbility == ABILITY_SYNCHRONIZE && (gHitMarker & HITMARKER_SYNCHRONIZE_EFFECT)) { - gHitMarker &= ~HITMARKER_SYNCHRONISE_EFFECT; + gHitMarker &= ~HITMARKER_SYNCHRONIZE_EFFECT; gBattleStruct->synchronizeMoveEffect &= ~(MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_CERTAIN); if (gBattleStruct->synchronizeMoveEffect == MOVE_EFFECT_TOXIC) gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_POISON; diff --git a/src/field_effect_helpers.c b/src/field_effect_helpers.c index ac4dac3ecb..320bfea578 100755 --- a/src/field_effect_helpers.c +++ b/src/field_effect_helpers.c @@ -26,8 +26,8 @@ static void UpdateFeetInFlowingWaterFieldEffect(struct Sprite *); static void UpdateAshFieldEffect_Wait(struct Sprite *); static void UpdateAshFieldEffect_Show(struct Sprite *); static void UpdateAshFieldEffect_End(struct Sprite *); -static void SynchroniseSurfAnim(struct ObjectEvent *, struct Sprite *); -static void SynchroniseSurfPosition(struct ObjectEvent *, struct Sprite *); +static void SynchronizeSurfAnim(struct ObjectEvent *, struct Sprite *); +static void SynchronizeSurfPosition(struct ObjectEvent *, struct Sprite *); static void UpdateBobbingEffect(struct ObjectEvent *, struct Sprite *, struct Sprite *); static void SpriteCB_UnderwaterSurfBlob(struct Sprite *); static u32 ShowDisguiseFieldEffect(u8, u8, u8); @@ -1053,13 +1053,13 @@ void UpdateSurfBlobFieldEffect(struct Sprite *sprite) { struct ObjectEvent *playerObj = &gObjectEvents[sprite->sPlayerObjId]; struct Sprite *playerSprite = &gSprites[playerObj->spriteId]; - SynchroniseSurfAnim(playerObj, sprite); - SynchroniseSurfPosition(playerObj, sprite); + SynchronizeSurfAnim(playerObj, sprite); + SynchronizeSurfPosition(playerObj, sprite); UpdateBobbingEffect(playerObj, playerSprite, sprite); sprite->oam.priority = playerSprite->oam.priority; } -static void SynchroniseSurfAnim(struct ObjectEvent *playerObj, struct Sprite *sprite) +static void SynchronizeSurfAnim(struct ObjectEvent *playerObj, struct Sprite *sprite) { // Indexes into sAnimTable_SurfBlob u8 surfBlobDirectionAnims[] = { @@ -1078,7 +1078,7 @@ static void SynchroniseSurfAnim(struct ObjectEvent *playerObj, struct Sprite *sp StartSpriteAnimIfDifferent(sprite, surfBlobDirectionAnims[playerObj->movementDirection]); } -void SynchroniseSurfPosition(struct ObjectEvent *playerObj, struct Sprite *sprite) +void SynchronizeSurfPosition(struct ObjectEvent *playerObj, struct Sprite *sprite) { u8 i; s16 x = playerObj->currentCoords.x; From afa42f5d29350bf0977a01b3c81ca82778cd52a8 Mon Sep 17 00:00:00 2001 From: mitsunee Date: Wed, 19 Nov 2025 21:19:15 +0100 Subject: [PATCH 03/18] Standardize spelling of "paralyze" --- asm/macros/battle_script.inc | 2 +- data/battle_scripts_1.s | 2 +- src/battle_script_commands.c | 6 +++--- src/battle_util.c | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 0bb9e072be..7209310fca 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1048,7 +1048,7 @@ .byte 0xcc .endm - .macro cureifburnedparalysedorpoisoned ptr:req + .macro cureifburnedparalyzedorpoisoned ptr:req .byte 0xcd .4byte \ptr .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 49ffeffb42..e04d68defd 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -2521,7 +2521,7 @@ BattleScript_EffectRefresh:: attackcanceler attackstring ppreduce - cureifburnedparalysedorpoisoned BattleScript_ButItFailed + cureifburnedparalyzedorpoisoned BattleScript_ButItFailed attackanimation waitanimation printstring STRINGID_PKMNSTATUSNORMAL diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 30f347a9d4..d5013b0239 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -281,7 +281,7 @@ static void Cmd_trymemento(void); static void Cmd_setforcedtarget(void); static void Cmd_setcharge(void); static void Cmd_callenvironmentattack(void); -static void Cmd_cureifburnedparalysedorpoisoned(void); +static void Cmd_cureifburnedparalyzedorpoisoned(void); static void Cmd_settorment(void); static void Cmd_jumpifnodamage(void); static void Cmd_settaunt(void); @@ -533,7 +533,7 @@ void (*const gBattleScriptingCommandsTable[])(void) = Cmd_setforcedtarget, //0xCA Cmd_setcharge, //0xCB Cmd_callenvironmentattack, //0xCC - Cmd_cureifburnedparalysedorpoisoned, //0xCD + Cmd_cureifburnedparalyzedorpoisoned, //0xCD Cmd_settorment, //0xCE Cmd_jumpifnodamage, //0xCF Cmd_settaunt, //0xD0 @@ -9116,7 +9116,7 @@ static void Cmd_callenvironmentattack(void) } // Refresh -static void Cmd_cureifburnedparalysedorpoisoned(void) +static void Cmd_cureifburnedparalyzedorpoisoned(void) { if (gBattleMons[gBattlerAttacker].status1 & (STATUS1_POISON | STATUS1_BURN | STATUS1_PARALYSIS | STATUS1_TOXIC_POISON)) { diff --git a/src/battle_util.c b/src/battle_util.c index f511a86764..ea721743dd 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -1992,7 +1992,7 @@ enum CANCELER_TAUNTED, CANCELER_IMPRISONED, CANCELER_CONFUSED, - CANCELER_PARALYSED, + CANCELER_PARALYZED, CANCELER_IN_LOVE, CANCELER_BIDE, CANCELER_THAW, @@ -2185,7 +2185,7 @@ u8 AtkCanceler_UnableToUseMove(void) } gBattleStruct->atkCancelerTracker++; break; - case CANCELER_PARALYSED: // paralysis + case CANCELER_PARALYZED: // paralysis if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_PARALYSIS) && (Random() % 4) == 0) { gProtectStructs[gBattlerAttacker].prlzImmobility = 1; From 7b6bdab12a2db0c480691ff5c3f96677bb477f2d Mon Sep 17 00:00:00 2001 From: Flit <8645405+FlitPix@users.noreply.github.com> Date: Wed, 19 Nov 2025 21:10:06 -0500 Subject: [PATCH 04/18] add arm-none-eabi-newlib to required arch linux packages --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 1cca7f8ae6..e6457dbb42 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -339,7 +339,7 @@ Then proceed to [Choosing where to store pokeemerald (Linux)](#choosing-where-to ### Arch Linux Run this command as root to install the necessary packages: ```bash -pacman -S base-devel arm-none-eabi-binutils git libpng +pacman -S base-devel arm-none-eabi-binutils arm-none-eabi-newlib git libpng ``` Then proceed to [Choosing where to store pokeemerald (Linux)](#choosing-where-to-store-pokeemerald-linux).
From 7085d30263359a800cde598c86f05045a930697e Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Thu, 27 Nov 2025 19:09:40 +0100 Subject: [PATCH 05/18] Fix substitute graphic not disappearing after using a pivor move (#8340) --- data/battle_scripts_1.s | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index a859d32cdf..5f0b650f72 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -156,7 +156,11 @@ BattleScript_EffectShedTail:: waitmessage B_WAIT_TIME_LONG moveendto MOVEEND_ATTACKER_VISIBLE moveendfrom MOVEEND_TARGET_VISIBLE - goto BattleScript_MoveSwitchOpenPartyScreen + call BattleScript_MoveSwitchOpenPartyScreenReturnWithNoAnim + switchinanim BS_ATTACKER, FALSE, TRUE + waitstate + switchineffects BS_ATTACKER + end BattleScript_EffectPsychicNoise:: printstring STRINGID_PKMNPREVENTEDFROMHEALING @@ -308,6 +312,14 @@ BattleScript_MoveSwitch: printstring STRINGID_PKMNWENTBACK waitmessage B_WAIT_TIME_SHORT BattleScript_MoveSwitchOpenPartyScreen:: + call BattleScript_MoveSwitchOpenPartyScreenReturnWithNoAnim + switchinanim BS_ATTACKER, FALSE, FALSE + waitstate + switchineffects BS_ATTACKER +BattleScript_MoveSwitchEnd: + end + +BattleScript_MoveSwitchOpenPartyScreenReturnWithNoAnim: openpartyscreen BS_ATTACKER, BattleScript_MoveSwitchEnd waitstate returntoball BS_ATTACKER, FALSE @@ -320,11 +332,7 @@ BattleScript_MoveSwitchOpenPartyScreen:: printstring STRINGID_EMPTYSTRING3 waitmessage 1 printstring STRINGID_SWITCHINMON - switchinanim BS_ATTACKER, FALSE, TRUE - waitstate - switchineffects BS_ATTACKER -BattleScript_MoveSwitchEnd: - end + return BattleScript_EffectPledge:: attackcanceler From c36fc5c4b7f741f7bec43fe70b0cc5303f0f4123 Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Thu, 27 Nov 2025 19:07:45 +0000 Subject: [PATCH 06/18] Fixes Beak Blast burning after Beak Blast was already used (#8361) --- asm/macros/battle_script.inc | 4 ---- data/battle_scripts_1.s | 1 - include/battle.h | 5 ++--- src/battle_script_commands.c | 21 ++++++++++++--------- test/battle/move_effect/beak_blast.c | 20 +++++++++++++++++--- 5 files changed, 31 insertions(+), 20 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 588360d226..a9584dcef6 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1766,10 +1766,6 @@ callnative BS_WaitFanfare .endm - .macro setbeakblast - callnative BS_SetBeakBlast - .endm - .macro cantarshotwork failInstr:req callnative BS_CanTarShotWork .4byte \failInstr diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 5f0b650f72..777481ac5e 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -671,7 +671,6 @@ BattleScript_EffectCourtChange:: goto BattleScript_MoveEnd BattleScript_BeakBlastSetUp:: - setbeakblast flushtextbox playanimation BS_ATTACKER, B_ANIM_BEAK_BLAST_SETUP, NULL printstring STRINGID_HEATUPBEAK diff --git a/include/battle.h b/include/battle.h index 1c7b824fd1..559b4eb88b 100755 --- a/include/battle.h +++ b/include/battle.h @@ -158,19 +158,18 @@ struct ProtectStruct u32 disableEjectPack:1; u32 pranksterElevated:1; u32 quickDraw:1; - u32 beakBlastCharge:1; u32 quash:1; u32 shellTrap:1; u32 eatMirrorHerb:1; u32 activateOpportunist:2; // 2 - to copy stats. 1 - stats copied (do not repeat). 0 - no stats to copy u16 usedAllySwitch:1; + u16 lashOutAffected:1; // End of 32-bit bitfield u32 helpingHand:3; - u16 lashOutAffected:1; u16 assuranceDoubled:1; u16 myceliumMight:1; u16 laggingTail:1; - u16 padding:9; + u16 padding:10; // End of 16-bit bitfield u16 physicalDmg; u16 specialDmg; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index e246c48fbb..bc66490054 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1069,6 +1069,15 @@ bool32 EmergencyExitCanBeTriggered(u32 battler) return FALSE; } +static inline bool32 IsBattlerUsingBeakBlast(u32 battler) +{ + if (gChosenActionByBattler[battler] != B_ACTION_USE_MOVE) + return FALSE; + if (GetMoveEffect(gChosenMoveByBattler[battler]) != EFFECT_BEAK_BLAST) + return FALSE; + return !HasBattlerActedThisTurn(battler); +} + static void Cmd_attackcanceler(void) { CMD_ARGS(); @@ -1295,7 +1304,8 @@ static void Cmd_attackcanceler(void) gBattleCommunication[MISS_TYPE] = B_MSG_PROTECTED; gBattlescriptCurrInstr = cmd->nextInstr; } - else if (gProtectStructs[gBattlerTarget].beakBlastCharge && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), gCurrentMove)) + else if (IsBattlerUsingBeakBlast(gBattlerTarget) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), gCurrentMove)) { gProtectStructs[gBattlerAttacker].touchedProtectLike = TRUE; gBattlescriptCurrInstr = cmd->nextInstr; @@ -6116,7 +6126,7 @@ static void Cmd_moveend(void) } // Not strictly a protect effect, but works the same way - if (gProtectStructs[gBattlerTarget].beakBlastCharge + if (IsBattlerUsingBeakBlast(gBattlerTarget) && CanBeBurned(gBattlerAttacker, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) { @@ -16299,13 +16309,6 @@ void BS_WaitFanfare(void) gBattlescriptCurrInstr = cmd->nextInstr; } -void BS_SetBeakBlast(void) -{ - NATIVE_ARGS(); - gProtectStructs[gBattlerAttacker].beakBlastCharge = TRUE; - gBattlescriptCurrInstr = cmd->nextInstr; -} - void BS_RemoveTerrain(void) { NATIVE_ARGS(); diff --git a/test/battle/move_effect/beak_blast.c b/test/battle/move_effect/beak_blast.c index b879a198c8..6e94a908e5 100644 --- a/test/battle/move_effect/beak_blast.c +++ b/test/battle/move_effect/beak_blast.c @@ -86,7 +86,7 @@ SINGLE_BATTLE_TEST("Beak Blast burns only when contact moves are used") PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_BEAK_BLAST); MOVE(opponent, move); } + TURN { MOVE(opponent, move); MOVE(player, MOVE_BEAK_BLAST); } TURN {} } SCENE { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_BEAK_BLAST_SETUP, player); @@ -115,11 +115,11 @@ SINGLE_BATTLE_TEST("Beak Blast burns only when contact moves are used") SINGLE_BATTLE_TEST("Beak Blast doesn't burn fire types") { GIVEN { - ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[0] == TYPE_FIRE || gSpeciesInfo[SPECIES_ARCANINE].types[1]); + ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[0] == TYPE_FIRE || gSpeciesInfo[SPECIES_ARCANINE].types[1] == TYPE_FIRE); PLAYER(SPECIES_ARCANINE); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_BEAK_BLAST); MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_BEAK_BLAST); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); NOT STATUS_ICON(player, burn: TRUE); @@ -127,6 +127,20 @@ SINGLE_BATTLE_TEST("Beak Blast doesn't burn fire types") } } +SINGLE_BATTLE_TEST("Beak Blast doesn't burn after being used") +{ + GIVEN { + ASSUME(GetMovePriority(MOVE_COUNTER) < GetMovePriority(MOVE_BEAK_BLAST)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_BEAK_BLAST); MOVE(player, MOVE_COUNTER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAK_BLAST, opponent); + NOT STATUS_ICON(player, burn: TRUE); + } +} + TO_DO_BATTLE_TEST("Beak Blast's charging message is shown regardless if it would've missed"); TO_DO_BATTLE_TEST("Beak Blast fails if it's forced by Encore after choosing a different move"); TO_DO_BATTLE_TEST("Bulletproof is immune to Beak Blast but not to the burn it causes"); From 8355164b97caff8b0cab781cffffbca026502f42 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Thu, 27 Nov 2025 21:08:37 +0100 Subject: [PATCH 07/18] Fix Roar not being recorded for LastUsedMove (#8362) --- src/battle_main.c | 1 - src/battle_script_commands.c | 29 ++++++++++++----------------- src/battle_util.c | 2 +- test/battle/move_effect/roar.c | 2 ++ 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/battle_main.c b/src/battle_main.c index 2b4b0aafd1..83c003150c 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3260,7 +3260,6 @@ void SwitchInClearSetData(u32 battler, struct Volatiles *volatilesCopy) } gBattleStruct->choicedMove[battler] = MOVE_NONE; - gCurrentMove = MOVE_NONE; gBattleStruct->arenaTurnCounter = 0xFF; // Restore struct member so replacement does not miss timing diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index bc66490054..16d15e644e 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6383,8 +6383,9 @@ static void Cmd_moveend(void) } } enum BattleMoveEffects originalEffect = GetMoveEffect(originallyUsedMove); - if (!(gAbsentBattlerFlags & (1u << gBattlerAttacker)) - && originalEffect != EFFECT_BATON_PASS && originalEffect != EFFECT_HEALING_WISH) + if (IsBattlerAlive(gBattlerAttacker) + && originalEffect != EFFECT_BATON_PASS + && originalEffect != EFFECT_HEALING_WISH) { if (gHitMarker & HITMARKER_OBEYS) { @@ -6918,19 +6919,17 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_THIRD_MOVE_BLOCK: - // Special case for Steel Roller since it has to check the chosen move - if (GetMoveEffect(gChosenMove) == EFFECT_STEEL_ROLLER && IsBattlerTurnDamaged(gBattlerTarget)) - { - BattleScriptCall(BattleScript_RemoveTerrain); - effect = TRUE; - gBattleScripting.moveendState++; - break; - } - switch (moveEffect) { + case EFFECT_STEEL_ROLLER: + if (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && IsBattlerTurnDamaged(gBattlerTarget)) + { + BattleScriptCall(BattleScript_RemoveTerrain); + effect = TRUE; + } case EFFECT_ICE_SPINNER: if (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY + && gLastPrintedMoves[gBattlerAttacker] == gCurrentMove && IsBattlerAlive(gBattlerAttacker) && IsBattlerTurnDamaged(gBattlerTarget)) { @@ -7062,11 +7061,7 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_DANCER: - if (gCurrentMove == MOVE_NONE) - originallyUsedMove = gChosenMove; // Fallback to chosen move in case attacker is switched out in the middle of an attack resolution (eg red card) - else - originallyUsedMove = gCurrentMove; - if (IsDanceMove(originallyUsedMove) && !gBattleStruct->snatchedMoveIsUsed) + if (IsDanceMove(gCurrentMove) && !gBattleStruct->snatchedMoveIsUsed) { u32 battler, nextDancer = 0; bool32 hasDancerTriggered = FALSE; @@ -7100,7 +7095,7 @@ static void Cmd_moveend(void) nextDancer = battler | 0x4; } } - if (nextDancer && AbilityBattleEffects(ABILITYEFFECT_MOVE_END_OTHER, nextDancer & 0x3, 0, 0, originallyUsedMove)) + if (nextDancer && AbilityBattleEffects(ABILITYEFFECT_MOVE_END_OTHER, nextDancer & 0x3, 0, 0, gCurrentMove)) effect = TRUE; } } diff --git a/src/battle_util.c b/src/battle_util.c index 5296798db4..647e21bb66 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -927,7 +927,7 @@ void HandleAction_ActionFinished(void) | HITMARKER_IGNORE_DISGUISE); ClearDamageCalcResults(); - gCurrentMove = 0; + gCurrentMove = MOVE_NONE; gBattleScripting.animTurn = 0; gBattleScripting.animTargetsHit = 0; gBattleStruct->dynamicMoveType = 0; diff --git a/test/battle/move_effect/roar.c b/test/battle/move_effect/roar.c index 5b1230f253..5c46e0cdf4 100644 --- a/test/battle/move_effect/roar.c +++ b/test/battle/move_effect/roar.c @@ -20,6 +20,8 @@ SINGLE_BATTLE_TEST("Roar switches the target with a random non-fainted replaceme } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_ROAR, player); MESSAGE("The opposing Bulbasaur was dragged out!"); + } THEN { + EXPECT_EQ(gLastUsedMove, MOVE_ROAR); } } From b037a0eccc64af202c0d79c9822e473e44310e4d Mon Sep 17 00:00:00 2001 From: Estellar <137097857+estellarc@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:10:58 -0300 Subject: [PATCH 08/18] Fedora install instructions (#8355) --- INSTALL.md | 1 + docs/install/linux/FEDORA.md | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 docs/install/linux/FEDORA.md diff --git a/INSTALL.md b/INSTALL.md index 072d43a858..47f00cf78f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -32,6 +32,7 @@ Distributions with instructions: - [Debian](docs/install/linux/DEBIAN.md) - [Arch Linux](docs/install/linux/ARCH_LINUX.md) - [NixOS](docs/install/linux/NIXOS.md) +- [Fedora](docs/install/linux/FEDORA.md) Other distributions have to infer what to do from [general instructions](docs/install/linux/OTHERS.md). diff --git a/docs/install/linux/FEDORA.md b/docs/install/linux/FEDORA.md new file mode 100644 index 0000000000..0dba9c1e78 --- /dev/null +++ b/docs/install/linux/FEDORA.md @@ -0,0 +1,6 @@ +# Fedora instructions +## Installing dependencies +Open a terminal and run the following command from the command line: +```console +sudo dnf install gcc g++ arm-none-eabi-binutils-cs arm-none-eabi-gcc-cs arm-none-eabi-newlib git libpng-devel python3 +``` From 21c420042194267b31655114ddc2ce6c1d5078a0 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Thu, 27 Nov 2025 21:12:23 +0100 Subject: [PATCH 09/18] Fix debug battle flag never being cleared (#8357) --- src/battle_setup.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/battle_setup.c b/src/battle_setup.c index 1505f6af65..86d3900f3c 100644 --- a/src/battle_setup.c +++ b/src/battle_setup.c @@ -1299,6 +1299,7 @@ static void CB2_EndTrainerBattle(void) { HandleBattleVariantEndParty(); + gIsDebugBattle = FALSE; if (FollowerNPCIsBattlePartner()) { RestorePartyAfterFollowerNPCBattle(); From bf361a41dffd58ea132fcb649b7b85dc60d1d253 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Fri, 28 Nov 2025 15:13:37 +0100 Subject: [PATCH 10/18] Indent unintented if statement (#8367) Co-authored-by: Hedara --- src/overworld.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/overworld.c b/src/overworld.c index e70cae880d..1afce582cd 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -856,8 +856,8 @@ void LoadMapFromCameraTransition(u8 mapGroup, u8 mapNum) TryUpdateRandomTrainerRematches(mapGroup, mapNum); #endif //FREE_MATCH_CALL -if (I_VS_SEEKER_CHARGING != 0) - MapResetTrainerRematches(mapGroup, mapNum); + if (I_VS_SEEKER_CHARGING != 0) + MapResetTrainerRematches(mapGroup, mapNum); DoTimeBasedEvents(); SetSavedWeatherFromCurrMapHeader(); @@ -922,8 +922,8 @@ static void LoadMapFromWarp(bool32 a1) TryUpdateRandomTrainerRematches(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum); #endif //FREE_MATCH_CALL -if (I_VS_SEEKER_CHARGING != 0) - MapResetTrainerRematches(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum); + if (I_VS_SEEKER_CHARGING != 0) + MapResetTrainerRematches(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum); if (a1 != TRUE) DoTimeBasedEvents(); From c7735d6d2c6fad6f8c6bccfcd4c298d87429b42b Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Fri, 28 Nov 2025 17:26:22 +0100 Subject: [PATCH 11/18] Fix known failing AI trace test (#8337) --- test/battle/ai/ai_doubles.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/battle/ai/ai_doubles.c b/test/battle/ai/ai_doubles.c index 24d40912c8..055b1c5f81 100644 --- a/test/battle/ai/ai_doubles.c +++ b/test/battle/ai/ai_doubles.c @@ -452,7 +452,6 @@ AI_DOUBLE_BATTLE_TEST("AI treats an ally's redirection ability appropriately (ge AI_DOUBLE_BATTLE_TEST("AI recognizes Volt Absorb received from Trace") { - KNOWN_FAILING; // MGriffin's PR that switched two turn charging moves in AI tests broke this test, waiting on a fix GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_MAGNETON); @@ -460,7 +459,7 @@ AI_DOUBLE_BATTLE_TEST("AI recognizes Volt Absorb received from Trace") OPPONENT(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_THUNDER_WAVE, MOVE_THUNDERSHOCK, MOVE_WATER_GUN); } OPPONENT(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_THUNDER_WAVE, MOVE_THUNDERSHOCK, MOVE_WATER_GUN); } } WHEN { - TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDERSHOCK); NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDER_WAVE); NOT_EXPECT_MOVE(opponentRight, MOVE_THUNDER_WAVE); } + TURN { NOT_EXPECT_MOVES(opponentLeft, MOVE_THUNDERSHOCK, MOVE_THUNDER_WAVE); NOT_EXPECT_MOVE(opponentRight, MOVE_THUNDER_WAVE); } } THEN { EXPECT(gAiLogicData->abilities[B_POSITION_PLAYER_RIGHT] == ABILITY_VOLT_ABSORB); } From 6ccce4342b753002f2aad21423d96eaac9cfe3a6 Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Sat, 29 Nov 2025 09:49:29 +0000 Subject: [PATCH 12/18] Update Lash Out description to clarify its effect (#8372) --- src/data/moves_info.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/moves_info.h b/src/data/moves_info.h index e03db10286..51f27ceec9 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -18672,8 +18672,8 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = { .name = COMPOUND_STRING("Lash Out"), .description = COMPOUND_STRING( - "If stats lowered during this\n" - "turn, power is doubled."), + "If user's stats were lowered\n" + "this turn, power is doubled."), .effect = EFFECT_LASH_OUT, .power = 75, .type = TYPE_DARK, From 0611cbe2744cda5d251c757104815f845c3ea2a3 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Sat, 29 Nov 2025 15:26:58 -0300 Subject: [PATCH 13/18] Restored encourageEncore flag to non-volatile status effects (#8387) --- src/data/battle_move_effects.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index 88d4075570..121a753161 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -20,6 +20,7 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = { .battleScript = BattleScript_EffectNonVolatileStatus, .battleTvScore = 0, // Handled within the battle TV functions + .encourageEncore = TRUE, }, [EFFECT_ABSORB] = From cbcb7202860cac4807c6ef4ab02a4a2c00273a76 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Sat, 29 Nov 2025 17:04:32 -0300 Subject: [PATCH 14/18] Wrote missing Fling tests (#8383) --- test/battle/move_effect/fling.c | 53 +++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/test/battle/move_effect/fling.c b/test/battle/move_effect/fling.c index 5435fbcafe..635ca3d15f 100644 --- a/test/battle/move_effect/fling.c +++ b/test/battle/move_effect/fling.c @@ -80,8 +80,35 @@ SINGLE_BATTLE_TEST("Fling fails for Pokémon with Klutz ability") } } -TO_DO_BATTLE_TEST("Fling fails if the item changes the Pokémon's form") -TO_DO_BATTLE_TEST("Fling works if the item changes a Pokémon's form but not the one holding it") //Eg. non-matching Mega Stones +SINGLE_BATTLE_TEST("Fling fails if the item changes the Pokémon's form") +{ + GIVEN { + PLAYER(SPECIES_GIRATINA_ORIGIN) { Item(ITEM_GRISEOUS_CORE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == ITEM_GRISEOUS_CORE); + } +} + +SINGLE_BATTLE_TEST("Fling works if the item changes a Pokémon's form but not the one holding it") +{ + GIVEN { + PLAYER(SPECIES_VENUSAUR) { Item(ITEM_BLASTOISINITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + NOT MESSAGE("But it failed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} SINGLE_BATTLE_TEST("Fling's thrown item can be regained with Recycle") { @@ -461,4 +488,24 @@ SINGLE_BATTLE_TEST("Fling deals damage based on items fling power") } } -TO_DO_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") +{ + 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]); + } +} From dfd8daac2e97d70c1e97ca0c4415ef9cc687b783 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sun, 30 Nov 2025 09:10:43 +0100 Subject: [PATCH 15/18] Fixes Neutralizing Gas / Mold Breaker / Dragon Darts interaction (#8389) --- include/battle.h | 2 +- src/battle_script_commands.c | 1 + src/battle_util.c | 20 ++++++------ test/battle/ability/neutralizing_gas.c | 42 ++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/include/battle.h b/include/battle.h index 559b4eb88b..57e92bccef 100755 --- a/include/battle.h +++ b/include/battle.h @@ -667,7 +667,7 @@ struct BattleStruct u8 multipleSwitchInState:2; u8 multipleSwitchInCursor:3; u8 sleepClauseNotBlocked:1; - u8 padding1:1; + u8 moldBreakerActive:1; u8 multipleSwitchInSortedBattlers[MAX_BATTLERS_COUNT]; void (*savedCallback)(void); u16 usedHeldItems[PARTY_SIZE][NUM_BATTLE_SIDES]; // For each party member and side. For harvest, recycle diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 16d15e644e..c9c5f6dec6 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -7020,6 +7020,7 @@ static void Cmd_moveend(void) gSpecialStatuses[gBattlerTarget].berryReduced = FALSE; gSpecialStatuses[gBattlerTarget].distortedTypeMatchups = FALSE; gBattleScripting.moveEffect = MOVE_EFFECT_NONE; + gBattleStruct->moldBreakerActive = FALSE; gBattleStruct->isAtkCancelerForCalledMove = FALSE; gBattleStruct->swapDamageCategory = FALSE; gBattleStruct->categoryOverride = FALSE; diff --git a/src/battle_util.c b/src/battle_util.c index 647e21bb66..06023975e9 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -490,6 +490,9 @@ void HandleAction_UseMove(void) gCurrentMove = gChosenMove = GetMaxMove(gBattlerAttacker, gCurrentMove); } + if (IsMoldBreakerTypeAbility(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) || MoveIgnoresTargetAbility(gCurrentMove)) + gBattleStruct->moldBreakerActive = TRUE; + moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); if (!HandleMoveTargetRedirection()) @@ -933,6 +936,7 @@ void HandleAction_ActionFinished(void) gBattleStruct->dynamicMoveType = 0; gBattleStruct->bouncedMoveIsUsed = FALSE; gBattleStruct->snatchedMoveIsUsed = FALSE; + gBattleStruct->moldBreakerActive = FALSE; gBattleScripting.moveendState = 0; gBattleCommunication[3] = 0; gBattleCommunication[4] = 0; @@ -5360,17 +5364,11 @@ bool32 IsMoldBreakerTypeAbility(u32 battler, u32 ability) return FALSE; } -static inline bool32 CanBreakThroughAbility(u32 battlerAtk, u32 battlerDef, u32 ability, u32 hasAbilityShield, u32 ignoreMoldBreaker) +static inline bool32 CanBreakThroughAbility(u32 battlerAtk, u32 battlerDef, u32 hasAbilityShield, u32 ignoreMoldBreaker) { - if (hasAbilityShield || ignoreMoldBreaker) + if (hasAbilityShield || ignoreMoldBreaker || battlerDef == battlerAtk) return FALSE; - - return ((IsMoldBreakerTypeAbility(battlerAtk, ability) || MoveIgnoresTargetAbility(gCurrentMove)) - && battlerDef != battlerAtk - && gAbilitiesInfo[gBattleMons[battlerDef].ability].breakable - && gBattlerByTurnOrder[gCurrentTurnActionNumber] == battlerAtk - && gActionsByTurnOrder[gCurrentTurnActionNumber] == B_ACTION_USE_MOVE - && gCurrentTurnActionNumber < gBattlersCount); + return gBattleStruct->moldBreakerActive && gAbilitiesInfo[gBattleMons[battlerDef].ability].breakable; } u32 GetBattlerAbilityNoAbilityShield(u32 battler) @@ -5401,7 +5399,7 @@ u32 GetBattlerAbilityInternal(u32 battler, u32 ignoreMoldBreaker, u32 noAbilityS && gBattleMons[battler].ability == ABILITY_COMATOSE) return ABILITY_NONE; - if (CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability, hasAbilityShield, ignoreMoldBreaker)) + if (CanBreakThroughAbility(gBattlerAttacker, battler, hasAbilityShield, ignoreMoldBreaker)) return ABILITY_NONE; return gBattleMons[battler].ability; @@ -5415,7 +5413,7 @@ u32 GetBattlerAbilityInternal(u32 battler, u32 ignoreMoldBreaker, u32 noAbilityS && (gBattleMons[battler].ability != ABILITY_NEUTRALIZING_GAS || gBattleMons[battler].volatiles.gastroAcid)) return ABILITY_NONE; - if (CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability, hasAbilityShield, ignoreMoldBreaker)) + if (CanBreakThroughAbility(gBattlerAttacker, battler, hasAbilityShield, ignoreMoldBreaker)) return ABILITY_NONE; return gBattleMons[battler].ability; diff --git a/test/battle/ability/neutralizing_gas.c b/test/battle/ability/neutralizing_gas.c index 426f4c969f..9971d53c44 100644 --- a/test/battle/ability/neutralizing_gas.c +++ b/test/battle/ability/neutralizing_gas.c @@ -353,3 +353,45 @@ SINGLE_BATTLE_TEST("Neutralizing Gas only displays exiting message for the last NOT MESSAGE("The effects of the neutralizing gas wore off!"); } } + +DOUBLE_BATTLE_TEST("Neutralizing Gas is active for the duration of a Spread Move even if Neutralizing Gas is no longer on the field") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_ORIGIN_PULSE) == MOVE_TARGET_BOTH); + PLAYER(SPECIES_WEEZING) { HP(1); Ability(ABILITY_NEUTRALIZING_GAS); } + PLAYER(SPECIES_GOLEM) { Ability(ABILITY_STURDY); } + OPPONENT(SPECIES_BASCULEGION) { Ability(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_ORIGIN_PULSE); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_NEUTRALIZING_GAS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ORIGIN_PULSE, opponentLeft); + HP_BAR(playerLeft); + HP_BAR(playerRight); + MESSAGE("Weezing fainted!"); + MESSAGE("Golem fainted!"); + NOT ABILITY_POPUP(playerRight, ABILITY_STURDY); + } +} + +DOUBLE_BATTLE_TEST("Neutralizing Gas is active until the last Dragon Darts hit even if Neutralizing Gas is no longer on the field") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_DARTS) == EFFECT_DRAGON_DARTS); + PLAYER(SPECIES_WEEZING) { HP(1); Ability(ABILITY_NEUTRALIZING_GAS); } + PLAYER(SPECIES_GOLEM) { HP(2); MaxHP(2); Ability(ABILITY_STURDY); } + OPPONENT(SPECIES_BASCULEGION) { Ability(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_DRAGON_DARTS, target: playerLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_NEUTRALIZING_GAS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, opponentLeft); + HP_BAR(playerLeft); + MESSAGE("Weezing fainted!"); + HP_BAR(playerRight); + NOT MESSAGE("Golem fainted!"); + ABILITY_POPUP(playerRight, ABILITY_STURDY); + } +} From 31999561483c95e2e751bd62a8b102d05fb03596 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sun, 30 Nov 2025 11:44:41 +0100 Subject: [PATCH 16/18] Fixes battle tv overwriting damage values (#8378) --- src/battle_tv.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/battle_tv.c b/src/battle_tv.c index 2fc8fad7b7..50871baa82 100644 --- a/src/battle_tv.c +++ b/src/battle_tv.c @@ -1253,6 +1253,7 @@ static void TrySetBattleSeminarShow(void) dmgByMove[gMoveSelectionCursor[gBattlerAttacker]] = gBattleStruct->moveDamage[gBattlerTarget]; // TODO: Not sure currMoveSaved = gCurrentMove; + u16 storedMoveResultFlags = gBattleStruct->moveResultFlags[gBattlerTarget]; for (i = 0; i < MAX_MON_MOVES; i++) { gCurrentMove = gBattleMons[gBattlerAttacker].moves[i]; @@ -1269,8 +1270,7 @@ static void TrySetBattleSeminarShow(void) ctx.updateFlags = FALSE; ctx.isSelfInflicted = FALSE; ctx.fixedBasePower = powerOverride; - gBattleStruct->moveDamage[gBattlerTarget] = CalculateMoveDamage(&ctx); - dmgByMove[i] = gBattleStruct->moveDamage[gBattlerTarget]; + dmgByMove[i] = CalculateMoveDamage(&ctx); if (dmgByMove[i] == 0 && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) dmgByMove[i] = 1; } @@ -1301,8 +1301,8 @@ static void TrySetBattleSeminarShow(void) } } - gBattleStruct->moveDamage[gBattlerTarget] = dmgByMove[gMoveSelectionCursor[gBattlerAttacker]]; gCurrentMove = currMoveSaved; + gBattleStruct->moveResultFlags[gBattlerTarget] = storedMoveResultFlags; } static bool8 ShouldCalculateDamage(u16 move, s32 *dmg, u16 *powerOverride) From d9aac4f12a759934570ecf27af6fc9ba0124c697 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sun, 30 Nov 2025 11:49:21 +0100 Subject: [PATCH 17/18] Fix ball cycling not working properly when the same ball take multiple bag slots (#8163) --- src/battle_controller_player.c | 52 ++++++++++++++-------------------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index f2819db600..93fd846a5b 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -197,46 +197,36 @@ static void CompleteOnBattlerSpritePosX_0(u32 battler) static u16 GetPrevBall(u16 ballId) { - u16 ballPrev; - s32 i, j; - CompactItemsInBagPocket(POCKET_POKE_BALLS); - for (i = 0; i < gBagPockets[POCKET_POKE_BALLS].capacity; i++) + s32 i; + s32 index = ItemIdToBallId(ballId); + u32 newBall = 0; + for (i = 0; i < POKEBALL_COUNT; i++) { - if (ballId == GetBagItemId(POCKET_POKE_BALLS, i)) - { - if (i <= 0) - { - for (j = gBagPockets[POCKET_POKE_BALLS].capacity - 1; j >= 0; j--) - { - ballPrev = GetBagItemId(POCKET_POKE_BALLS, j); - if (ballPrev != ITEM_NONE) - return ballPrev; - } - } - i--; - break; - } + index--; + if (index == -1) + index = POKEBALL_COUNT - 1; + newBall = gBallItemIds[index]; + if (CheckBagHasItem(newBall, 1)) + return newBall; } - return GetBagItemId(POCKET_POKE_BALLS, i); + return ballId; } static u32 GetNextBall(u32 ballId) { - u32 ballNext = ITEM_NONE; s32 i; - CompactItemsInBagPocket(POCKET_POKE_BALLS); - for (i = 1; i < gBagPockets[POCKET_POKE_BALLS].capacity; i++) + s32 index = ItemIdToBallId(ballId); + u32 newBall = 0; + for (i = 0; i < POKEBALL_COUNT; i++) { - if (ballId == GetBagItemId(POCKET_POKE_BALLS, i-1)) - { - ballNext = GetBagItemId(POCKET_POKE_BALLS, i); - break; - } + index++; + if (index == POKEBALL_COUNT) + index = 0; + newBall = gBallItemIds[index]; + if (CheckBagHasItem(newBall, 1)) + return newBall; } - if (ballNext == ITEM_NONE) - return GetBagItemId(POCKET_POKE_BALLS, 0); // Zeroth slot - else - return ballNext; + return ballId; } static void HandleInputChooseAction(u32 battler) From f42117a9d8064db3f126e338b9c52864f0cc9467 Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Sun, 30 Nov 2025 05:56:07 -0500 Subject: [PATCH 18/18] Fix switchin KO threshold logic (#8370) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- src/battle_ai_switch_items.c | 4 ++-- test/battle/ai/ai_switching.c | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 29b58ed9e6..02cc668d14 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -2240,7 +2240,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, // Check if current mon can revenge kill in some capacity // If AI mon can one shot - if (damageDealt > playerMonHP) + if (damageDealt >= playerMonHP) { if (canSwitchinWin1v1) { @@ -2252,7 +2252,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, } // If AI mon can two shot - if (damageDealt > playerMonHP / 2) + if (damageDealt >= (playerMonHP / 2 + playerMonHP % 2)) // Modulo to handle odd numbers in non-decimal division { if (canSwitchinWin1v1) { diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 329add5a07..947c9209c2 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -1459,3 +1459,23 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will consider choice-locked TURN { MOVE(player, MOVE_MIGHTY_CLEAVE); EXPECT_MOVE(opponent, MOVE_TACKLE); item == ITEM_NONE ? EXPECT_SEND_OUT(opponent, 1) : EXPECT_SEND_OUT(opponent, 2); } } } + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI considers both meeting and exceeding KO thresholds correctly") +{ + u32 hp; + PARAMETRIZE { hp = 40; } + PARAMETRIZE { hp = 80; } + PARAMETRIZE { hp = 79; } + PARAMETRIZE { hp = 81; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_RAGE) == EFFECT_FIXED_HP_DAMAGE); + ASSUME(GetMoveFixedHPDamage(MOVE_DRAGON_RAGE) == 40); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_ZIGZAGOON) { Speed(5); HP(hp); Moves(MOVE_PROTECT, MOVE_TACKLE); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(6); Moves(MOVE_EXPLOSION); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(6); Moves(MOVE_DRAGON_RAGE); } + OPPONENT(SPECIES_BELDUM) { Speed(4); Moves(MOVE_TACKLE); } + } WHEN { + TURN { MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_EXPLOSION); hp > 80 ? EXPECT_SEND_OUT(opponent, 2) : EXPECT_SEND_OUT(opponent, 1); } + } +}