From 7b3a2f3aaf4777ea0401f4b52b65289f6a365e7b Mon Sep 17 00:00:00 2001 From: khbsd Date: Sat, 25 Oct 2025 21:15:03 -0500 Subject: [PATCH 01/12] fix: hypertraining a stat now optionally reflects in the summary screen (#8035) --- include/config/summary_screen.h | 1 + src/pokemon_summary_screen.c | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/include/config/summary_screen.h b/include/config/summary_screen.h index 4b5f434cb3..07bae63533 100644 --- a/include/config/summary_screen.h +++ b/include/config/summary_screen.h @@ -8,6 +8,7 @@ #define P_SUMMARY_SCREEN_RENAME TRUE // If TRUE, an option to change Pokémon nicknames replaces the cancel prompt on the summary screen info page. #define P_SUMMARY_SCREEN_IV_EV_INFO FALSE // If TRUE, will allow player to cycle through the Stats, IVs, and EVs in the summary screen skills page. #define P_SUMMARY_SCREEN_IV_EV_BOX_ONLY FALSE // If TRUE, will allow player to cycle through the Stats, IVs, and EVs in the summary screen skills page, but only in the PC storage box. +#define P_SUMMARY_SCREEN_IV_HYPERTRAIN TRUE // If TRUE, stats that have been hyper trained will show as 31/S when viewing them in the summary screen #define P_SUMMARY_SCREEN_IV_EV_TILESET FALSE // If TRUE, loads an alternate tileset to allow changing the "STATS" label in the summary screen skills page. Note: if it's still loading the alternate tileset after changing this and recompiling, you may need a `make clean` before compilation. #define P_SUMMARY_SCREEN_IV_EV_VALUES FALSE // If TRUE, will show the actual IV value instead of the letter grade. /* diff --git a/src/pokemon_summary_screen.c b/src/pokemon_summary_screen.c index 1f69af7e96..877343ea68 100644 --- a/src/pokemon_summary_screen.c +++ b/src/pokemon_summary_screen.c @@ -334,6 +334,7 @@ static const u8 *GetLetterGrade(u32 stat); static u8 AddWindowFromTemplateList(const struct WindowTemplate *template, u8 templateId); static u8 IncrementSkillsStatsMode(u8 mode); static void ClearStatLabel(u32 length, u32 statsCoordX, u32 statsCoordY); +u32 GetAdjustedIvData(struct Pokemon *mon, u32 stat); static const struct BgTemplate sBgTemplates[] = { @@ -1173,6 +1174,13 @@ static void DestroyCategoryIcon(void) sMonSummaryScreen->categoryIconSpriteId = 0xFF; } +u32 GetAdjustedIvData(struct Pokemon *mon, u32 stat) +{ + if (P_SUMMARY_SCREEN_IV_HYPERTRAIN && GetMonData(mon, MON_DATA_HYPER_TRAINED_HP + stat)) + return MAX_PER_STAT_IVS; + return GetMonData(mon, MON_DATA_HP_IV + stat); +} + void ShowPokemonSummaryScreen(u8 mode, void *mons, u8 monIndex, u8 maxMonIndex, void (*callback)(void)) { sMonSummaryScreen = AllocZeroed(sizeof(*sMonSummaryScreen)); @@ -1864,12 +1872,12 @@ void ExtractMonSkillStatsData(struct Pokemon *mon, struct PokeSummary *sum) void ExtractMonSkillIvData(struct Pokemon *mon, struct PokeSummary *sum) { - sum->currentHP = GetMonData(mon, MON_DATA_HP_IV); - sum->atk = GetMonData(mon, MON_DATA_ATK_IV); - sum->def = GetMonData(mon, MON_DATA_DEF_IV); - sum->spatk = GetMonData(mon, MON_DATA_SPATK_IV); - sum->spdef = GetMonData(mon, MON_DATA_SPDEF_IV); - sum->speed = GetMonData(mon, MON_DATA_SPEED_IV); + sum->currentHP = GetAdjustedIvData(mon, STAT_HP); + sum->atk = GetAdjustedIvData(mon, STAT_ATK); + sum->def = GetAdjustedIvData(mon, STAT_DEF); + sum->spatk = GetAdjustedIvData(mon, STAT_SPATK); + sum->spdef = GetAdjustedIvData(mon, STAT_SPDEF); + sum->speed = GetAdjustedIvData(mon, STAT_SPEED); } void ExtractMonSkillEvData(struct Pokemon *mon, struct PokeSummary *sum) From 95d98305dd52ad0763ac86cfa52783d982ce21ba Mon Sep 17 00:00:00 2001 From: Raymond Dodge Date: Sun, 26 Oct 2025 06:45:40 -0400 Subject: [PATCH 02/12] Add camera-facing right-walking Krabby and Kingler follower sprites (#7881) --- graphics/pokemon/kingler/overworld.png | Bin 914 -> 1046 bytes graphics/pokemon/krabby/overworld.png | Bin 728 -> 567 bytes src/data/pokemon/species_info/gen_1_families.h | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/graphics/pokemon/kingler/overworld.png b/graphics/pokemon/kingler/overworld.png index 135f79b3265f3a35e81c0896d3f955b571b4adb3..c67ce3a53af2d0dd838dbcff67818aa40e67ecfc 100644 GIT binary patch delta 1028 zcmV+f1pE7v2bKsWiBL{Q4GJ0x0000DNk~Le000310000W1Oos701t<|*8l(j9+5I4 ze@mlhnE(I-vq?ljRCr$Pl`(JQHWYv*z(0UbBy(Oxx!onI3A%|E zH;6W{Uc7&hge1#X0h;bn5DDW=&fXCcf8xO_;rjsq`+}{5*1}Y+^Mi0@QH+GE)vAC~ z00pguTqyv;wU8c3sWMubsFi!B5zeR?F$JN}nk!EZJ0(g{!B9G&$0y(a4t^+ay zfl;^vwqa=#;wxoX;+e*AB=q}KsDed869s5p#w<|CQAnIAKwgCDE@S>BLx#k2fAsF9 z`JP2W&j}!tu%<^&iTz~zOmS_Rg^kL6kQZ7fv}u(i>m7)|;D4fmStqnXn1W{mu-A-) z`;r27EwLM(tK4eYTr&oFA!MfHVwYr!A(y~CyCC$bJlQ+WgAju}=fE{1VIe4>+O-gI zVnF3348)l-r4I_iy2Sty0(4*ue{ul^1CWCdg8~rCz3J_swNiaOzY z46wB>J^LVRO1@inr*bs@*s3UferjLctNMti+RvB!ovEdUARTzi_!yeuLC<_DZJ2Fj!!82dmH#I zh?iDqQxzfzGYx@jBYQ76=$}75-*6`+E{8f2-hKH?pT5ho-?m;@Upn{FZC?YI7=%1r zT34iPeQ`owZx^0e5I#PBe?y5tAg;~AM)LaG?d|D%jKWD*PFQI8+PE7q^H!o@^BdH6 z&r01rw=g*#rkUyQcs$xuX^vNXF#Rl);&ia*aao@QCq|+5LMy@d%?y$`?7Lq+RmGP- z2jTiQ6U;2xVi=F(VcT7Cw>`{;(=?3dap_5i=@fDLN9TpZ>x1LXe|hGwZ+vs#onETq z@LXM*ZX(Jwx=YSsiZtRyli#!rbcztxj+j}!OHiMnHcPz#0`FEv$Jl=Q0 zakntwel`jtSvoevU7yTB7B2_0_%|a-c^pBd9wB5R%>0kf-~blyKjv@B<^N!t(|%0=00000oQd{w)L)K7q~4Sz_(%@jli9{@}0hukj> zyjesUe4EnghY+QQ9-vBYKga+_j*HpMS zuzE)lM-vM%5 zV|vhabO)3m1b=n1?8?HU0V_W$pgwWk6B286!C4=d)B%Wi2TEt$>6+63HH=bVpwl~p z6_Aupy-5Q2!lI(H&ugzYgCn3WqnJ-4Xr_NDg-w8b2K=hqVqrQNJOgS5H-NJNzUsrV zbpT?rL||(p=h-&)6|Tz!5x?t_Kx4Xq2FPH&B;=zQIDZ_Vtlf)#!8iaXC028UG}E=T z1#k(wTj|-)oIaodvY+H^fLsZssxXsh9oH#9S}LGf901b7wmoGELY*66xPq8+BEN1< zdIhX}hXXDKQqr^+y%{|CG+c=K-zQ?8X#lakm^l9oALL0XYc^b zoO$0p(YVMvUHS$(S%InYU9HbHJ{T&mIE75a=(4{jr! zNr#hE!BMDsa`|i!sGShK_SQ~5e&lW=l8phsB=oW*o#&!&y57&a84OH0nw)_BTr8~0nChA*p48j9i1SK)8{`Q|@szW@^Y V6i2-A!q5N!002ovPDHLkV1iy~>6g-l;nG{gfBZzoH7JlD;`Rtqg%-GUJd2<|~U|%quS(u|`(d=bzudzFi0G zPZ!6{NQ|eksEIIPbpa2BaKvwrEh(vC$*Lh7&-I73@5_w!+4DI?t|qOg5t+@NXClm0 z!XMF~)e;7*+q@75t>;BJF4*JtxMdXz#=>!$4IAFv(}0Qw#X%tsqFG4*h=k)CE6)jR cczb{32OcVXK-Skw7ZwCq0A(3Y zBq*$KusE=y;lzmxFJAmu&=8Pid&Cwf!IEJ=%)F@g$1~CU0YtcUE3G?e~xkY?e3p&2orQo2&b71bi;?x8|U!O)F zu`Vv3=Izyq`*N^JEqHZJ#MM?#F!%Bgz8B6O^X4yzzIU^{*x6y~s_oZ}XJxQ^ ze?PD-@AVCn#DxJn-%l=JxmC_oa+gW0A=*M%@s(UgZtep9y-ZJk>y&uL_e|ojC@WbV zBvragk?|#8MyyYLeL`TX9p_(()RiC3R6erOk+mweeC%BodoFegW5rCdWqlndRo4ql z_^8#M_%A+2@txp&NBQZekNj9$wej1P^XsRy&0C~gcr{Du zxOB&{r?MyXcfJqo>FepAD)>-~!-DIhW4R~8q|TRon=VLh_& Date: Sun, 26 Oct 2025 14:40:55 +0100 Subject: [PATCH 03/12] Fix PC turning on/off animation not working in Battle Frontier (#8048) --- src/field_specials.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/field_specials.c b/src/field_specials.c index 09fca47899..cd7a2a798f 100644 --- a/src/field_specials.c +++ b/src/field_specials.c @@ -22,12 +22,13 @@ #include "item.h" #include "item_icon.h" #include "link.h" -#include "load_save.h" #include "list_menu.h" +#include "load_save.h" #include "main.h" -#include "mystery_gift.h" #include "match_call.h" #include "menu.h" +#include "metatile_behavior.h" +#include "mystery_gift.h" #include "overworld.h" #include "party_menu.h" #include "pokeblock.h" @@ -984,7 +985,7 @@ void FieldShowRegionMap(void) static bool32 IsBuildingPCTile(u32 tileId) { - return gMapHeader.mapLayout->primaryTileset == &gTileset_Building && (tileId == METATILE_Building_PC_On || tileId == METATILE_Building_PC_Off); + return (MetatileBehavior_IsPC(UNPACK_BEHAVIOR(GetMetatileAttributesById(tileId)))); } static bool32 IsPlayerHousePCTile(u32 tileId) From a9486a5521af2cf2a1cab78df30fcc60411ce0cf Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sun, 26 Oct 2025 15:28:40 +0100 Subject: [PATCH 04/12] Fix battle arena counting all judges loss for the opponent (#8046) --- src/battle_script_commands.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index dc6ce74ecd..00963383b1 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -17102,11 +17102,14 @@ void BS_ArenaJudgmentWindow(void) gBattlescriptCurrInstr = cmd->nextInstr; } -static void SetArenMonLostValues(u32 battler) +static void SetArenMonLostValues(u32 battler, u32 side) { gBattleMons[battler].hp = 0; gHitMarker |= HITMARKER_FAINTED(battler); - gBattleStruct->arenaLostOpponentMons |= 1u << gBattlerPartyIndexes[battler]; + if (side == B_SIDE_PLAYER) + gBattleStruct->arenaLostPlayerMons |= 1u << gBattlerPartyIndexes[battler]; + else + gBattleStruct->arenaLostOpponentMons |= 1u << gBattlerPartyIndexes[battler]; gDisableStructs[battler].truantSwitchInHack = TRUE; } @@ -17115,22 +17118,22 @@ static void SetArenMonLostValues(u32 battler) void BS_ArenaOpponentMonLost(void) { NATIVE_ARGS(); - SetArenMonLostValues(opponentMon); + SetArenMonLostValues(opponentMon, B_SIDE_OPPONENT); gBattlescriptCurrInstr = cmd->nextInstr; } void BS_ArenaPlayerMonLost(void) { NATIVE_ARGS(); - SetArenMonLostValues(playerMon); + SetArenMonLostValues(playerMon, B_SIDE_PLAYER); gBattlescriptCurrInstr = cmd->nextInstr; } void BS_ArenaBothMonsLost(void) { NATIVE_ARGS(); - SetArenMonLostValues(playerMon); - SetArenMonLostValues(opponentMon); + SetArenMonLostValues(playerMon, B_SIDE_PLAYER); + SetArenMonLostValues(opponentMon, B_SIDE_OPPONENT); gBattlescriptCurrInstr = cmd->nextInstr; } #undef playerMon From 6faf35dde90f9fd46f15fd7baddadaaaad825f9b Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sun, 26 Oct 2025 22:35:27 +0100 Subject: [PATCH 05/12] Fix emergency exit not triggering properly during wild battles (#8037) --- src/battle_end_turn.c | 3 +- src/battle_script_commands.c | 3 +- test/battle/ability/emergency_exit.c | 66 ++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/battle_end_turn.c b/src/battle_end_turn.c index 0c7807bece..d423659be9 100644 --- a/src/battle_end_turn.c +++ b/src/battle_end_turn.c @@ -311,13 +311,12 @@ static bool32 HandleEndTurnEmergencyExit(u32 battler) && IsBattlerAlive(battler) && (CanBattlerSwitch(battler) || !(gBattleTypeFlags & BATTLE_TYPE_TRAINER)) && !(gBattleTypeFlags & BATTLE_TYPE_ARENA) - && CountUsablePartyMons(battler) > 0 && gBattleMons[battler].volatiles.semiInvulnerable != STATE_SKY_DROP) // Not currently held by Sky Drop { gBattlerAbility = battler; gLastUsedAbility = ability; - if (gBattleTypeFlags & BATTLE_TYPE_TRAINER || IsOnPlayerSide(battler)) + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) BattleScriptExecute(BattleScript_EmergencyExitEnd2); else BattleScriptExecute(BattleScript_EmergencyExitWildEnd2); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 00963383b1..b8c12c1c5e 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1079,7 +1079,6 @@ bool32 EmergencyExitCanBeTriggered(u32 battler) && HadMoreThanHalfHpNowDoesnt(battler) && (CanBattlerSwitch(battler) || !(gBattleTypeFlags & BATTLE_TYPE_TRAINER)) && !(gBattleTypeFlags & BATTLE_TYPE_ARENA) - && CountUsablePartyMons(battler) > 0 && gBattleMons[battler].volatiles.semiInvulnerable != STATE_SKY_DROP) return TRUE; @@ -6696,7 +6695,7 @@ static void Cmd_moveend(void) effect = TRUE; gBattleScripting.battler = battler; - if (gBattleTypeFlags & BATTLE_TYPE_TRAINER || IsOnPlayerSide(battler)) + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) BattleScriptCall(BattleScript_EmergencyExit); else BattleScriptCall(BattleScript_EmergencyExitWild); diff --git a/test/battle/ability/emergency_exit.c b/test/battle/ability/emergency_exit.c index 5027d929ec..1dcd0be21b 100644 --- a/test/battle/ability/emergency_exit.c +++ b/test/battle/ability/emergency_exit.c @@ -108,3 +108,69 @@ SINGLE_BATTLE_TEST("Emergency Exit activates when taking residual damage and fal ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); } } + +WILD_BATTLE_TEST("Emergency Exit makes the pokemon flee during wild battle") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); }; + } WHEN { + TURN { MOVE(player, MOVE_SUPER_FANG);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } THEN { + EXPECT_EQ(gBattleOutcome, B_OUTCOME_MON_TELEPORTED); + } +} + +WILD_BATTLE_TEST("Emergency Exit activates when taking residual damage and falling under 50% max-hp (wild battle)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(134); Status1(STATUS1_BURN); }; + } WHEN { + TURN { } + } SCENE { + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } THEN { + EXPECT_EQ(gBattleOutcome, B_OUTCOME_MON_TELEPORTED); + } +} + +WILD_BATTLE_TEST("Emergency Exit makes the player ran during wild battle") +{ + GIVEN { + PLAYER(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); }; + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUPER_FANG);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_EMERGENCY_EXIT); + } THEN { + EXPECT_EQ(gBattleOutcome, B_OUTCOME_PLAYER_TELEPORTED); + } +} + +WILD_BATTLE_TEST("Emergency Exit activates when taking residual damage and falling under 50% max-hp (wild battle player side)") +{ + GIVEN { + PLAYER(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(134); }; + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SANDSTORM);} + } SCENE { + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_EMERGENCY_EXIT); + } THEN { + EXPECT_EQ(gBattleOutcome, B_OUTCOME_PLAYER_TELEPORTED); + } +} From b82a5a4c1cbed2a34be6a49d45c0cbc177bbec04 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Tue, 28 Oct 2025 17:02:35 +0100 Subject: [PATCH 06/12] Fix wrong gimmick spite showing when inputting too fast (#8066) --- include/battle_gimmick.h | 1 + src/battle_controller_player.c | 2 ++ src/battle_gimmick.c | 7 +++++++ 3 files changed, 10 insertions(+) diff --git a/include/battle_gimmick.h b/include/battle_gimmick.h index 42b8c7255a..84b1d6b26a 100644 --- a/include/battle_gimmick.h +++ b/include/battle_gimmick.h @@ -35,6 +35,7 @@ void SetGimmickAsActivated(u32 battler, enum Gimmick gimmick); void ChangeGimmickTriggerSprite(u32 spriteId, u32 animId); void CreateGimmickTriggerSprite(u32 battler); bool32 IsGimmickTriggerSpriteActive(void); +bool32 IsGimmickTriggerSpriteMatchingBattler(u32 battler); void HideGimmickTriggerSprite(void); void DestroyGimmickTriggerSprite(void); diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index 3361ce2f65..856bf5d30b 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -2090,6 +2090,8 @@ void PlayerHandleChooseMove(u32 battler) if (!IsGimmickTriggerSpriteActive()) gBattleStruct->gimmick.triggerSpriteId = 0xFF; + else if (!IsGimmickTriggerSpriteMatchingBattler(battler)) + DestroyGimmickTriggerSprite(); if (!(gBattleStruct->gimmick.usableGimmick[battler] == GIMMICK_Z_MOVE && !gBattleStruct->zmove.viable)) CreateGimmickTriggerSprite(battler); diff --git a/src/battle_gimmick.c b/src/battle_gimmick.c index 76ea80a4f6..38d60d4365 100644 --- a/src/battle_gimmick.c +++ b/src/battle_gimmick.c @@ -178,6 +178,13 @@ bool32 IsGimmickTriggerSpriteActive(void) return FALSE; } +bool32 IsGimmickTriggerSpriteMatchingBattler(u32 battler) +{ + if (battler == gSprites[gBattleStruct->gimmick.triggerSpriteId].tBattler) + return TRUE; + return FALSE; +} + void HideGimmickTriggerSprite(void) { if (gBattleStruct->gimmick.triggerSpriteId != 0xFF) From 3b66cd007bccbdf6151d84303b1027ef291be4d5 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Tue, 28 Oct 2025 17:19:52 +0100 Subject: [PATCH 07/12] Fix target cancelling not working properly with z-move (#8067) --- src/battle_controller_player.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index 856bf5d30b..b76a72af9b 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -454,6 +454,12 @@ void HandleInputChooseTarget(u32 battler) PlaySE(SE_SELECT); gSprites[gBattlerSpriteIds[gMultiUsePlayerCursor]].callback = SpriteCB_HideAsMoveTarget; gBattlerControllerFuncs[battler] = HandleInputChooseMove; + if (gBattleStruct->gimmick.playerSelect == 1 && gBattleStruct->gimmick.usableGimmick[battler] == GIMMICK_Z_MOVE) + { + gBattleStruct->gimmick.playerSelect = 0; + gBattleStruct->zmove.viewing = TRUE; + ReloadMoveNames(battler); + } DoBounceEffect(battler, BOUNCE_HEALTHBOX, 7, 1); DoBounceEffect(battler, BOUNCE_MON, 7, 1); EndBounceEffect(gMultiUsePlayerCursor, BOUNCE_HEALTHBOX); From 96f6e9948664ee0189599b95183a8f3be062f216 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:22:41 +0100 Subject: [PATCH 08/12] add MandL27 as a contributor for code (#8064) Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ CREDITS.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index aa13a3864d..a54e1caefd 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -441,6 +441,15 @@ "contributions": [ "code" ] + }, + { + "login": "MandL27", + "name": "MandL27", + "avatar_url": "https://avatars.githubusercontent.com/u/10366615?v=4", + "profile": "https://github.com/MandL27", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/CREDITS.md b/CREDITS.md index d3d46d0361..547b5bd52d 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -74,6 +74,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d ghostyboyy97
ghostyboyy97

💻 Marky
Marky

💻 + MandL27
MandL27

💻 From a9fc0b28c94446f83c327aefb09a88c4cd5aeff0 Mon Sep 17 00:00:00 2001 From: grintoul <166724814+grintoul1@users.noreply.github.com> Date: Tue, 28 Oct 2025 18:23:41 +0000 Subject: [PATCH 09/12] Corrects battler partner identification in battle_ai_switch_items.c (#8071) --- src/battle_ai_switch_items.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 96cb7e30df..f300741675 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -1124,7 +1124,7 @@ bool32 ShouldSwitch(u32 battler) if (IsDoubleBattle()) { - u32 partner = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerAtPosition(battler))); + u32 partner = BATTLE_PARTNER(battler); battlerIn1 = battler; if (gAbsentBattlerFlags & (1u << partner)) battlerIn2 = battler; @@ -1275,7 +1275,7 @@ void ModifySwitchAfterMoveScoring(u32 battler) if (IsDoubleBattle()) { - u32 partner = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerAtPosition(battler))); + u32 partner = BATTLE_PARTNER(battler); battlerIn1 = battler; if (gAbsentBattlerFlags & (1u << partner)) battlerIn2 = battler; @@ -1323,7 +1323,7 @@ bool32 IsSwitchinValid(u32 battler) // Edge case: See if partner already chose to switch into the same mon if (IsDoubleBattle()) { - u32 partner = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerAtPosition(battler))); + u32 partner = BATTLE_PARTNER(battler); if (gBattleStruct->AI_monToSwitchIntoId[battler] == PARTY_SIZE) // Generic switch { if ((gAiLogicData->shouldSwitch & (1u << partner)) && gAiLogicData->monToSwitchInId[partner] == gAiLogicData->mostSuitableMonId[battler]) From c90e6952f32466ed165565c561cbf862fdb90d74 Mon Sep 17 00:00:00 2001 From: grintoul <166724814+grintoul1@users.noreply.github.com> Date: Tue, 28 Oct 2025 18:26:10 +0000 Subject: [PATCH 10/12] Fix Ally Switch being useable in Frontier Link Multi battles (#8059) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- include/battle_util.h | 1 + include/constants/battle.h | 1 + src/battle_script_commands.c | 4 +--- src/battle_tower.c | 2 +- src/battle_util.c | 9 +++++++++ 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/include/battle_util.h b/include/battle_util.h index 3531f0eb6b..8601c792f6 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -414,5 +414,6 @@ bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, u32 abilityAtk, u u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect); bool32 IsSemiInvulnerable(u32 battler, enum SemiInvulnerableExclusion excludeCommander); bool32 BreaksThroughSemiInvulnerablity(u32 battler, u32 move); +bool32 HasPartnerTrainer(u32 battler); #endif // GUARD_BATTLE_UTIL_H diff --git a/include/constants/battle.h b/include/constants/battle.h index 825487f24e..012b8986ef 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -99,6 +99,7 @@ enum BattlerId #define RECORDED_WILD_BATTLE ((gBattleTypeFlags & BATTLE_TYPE_RECORDED) && !(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FRONTIER))) #define BATTLE_TWO_VS_ONE_OPPONENT ((gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && TRAINER_BATTLE_PARAM.opponentB == 0xFFFF)) #define BATTLE_TYPE_HAS_AI (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER | BATTLE_TYPE_INGAME_PARTNER) +#define BATTLE_TYPE_PLAYER_HAS_PARTNER (BATTLE_TYPE_MULTI | BATTLE_TYPE_INGAME_PARTNER | BATTLE_TYPE_TOWER_LINK_MULTI) // Battle Outcome defines #define B_OUTCOME_WON 1 diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index b8c12c1c5e..223a4bd382 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -15711,9 +15711,7 @@ void BS_TryAllySwitch(void) { NATIVE_ARGS(const u8 *failInstr); - if (!IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker)) - || (GetBattlerSide(gBattlerAttacker) == B_SIDE_PLAYER && gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) - || (GetBattlerSide(gBattlerAttacker) == B_SIDE_OPPONENT && gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS)) + if (!IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker)) || HasPartnerTrainer(gBattlerAttacker)) { gBattlescriptCurrInstr = cmd->failInstr; } diff --git a/src/battle_tower.c b/src/battle_tower.c index e215c259a4..a03f0a130b 100644 --- a/src/battle_tower.c +++ b/src/battle_tower.c @@ -1987,7 +1987,7 @@ void DoSpecialTrainerBattle(void) gBattleTypeFlags |= BATTLE_TYPE_DOUBLE | BATTLE_TYPE_INGAME_PARTNER | BATTLE_TYPE_MULTI | BATTLE_TYPE_TWO_OPPONENTS; break; case FRONTIER_MODE_LINK_MULTIS: - gBattleTypeFlags |= BATTLE_TYPE_DOUBLE | BATTLE_TYPE_LINK | BATTLE_TYPE_MULTI | BATTLE_TYPE_TOWER_LINK_MULTI; + gBattleTypeFlags |= BATTLE_TYPE_DOUBLE | BATTLE_TYPE_LINK | BATTLE_TYPE_MULTI | BATTLE_TYPE_TOWER_LINK_MULTI | BATTLE_TYPE_TWO_OPPONENTS; FillFrontierTrainersParties(FRONTIER_MULTI_PARTY_SIZE); break; } diff --git a/src/battle_util.c b/src/battle_util.c index 8700fe2b2e..2e2f18cbc9 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -11907,3 +11907,12 @@ bool32 BreaksThroughSemiInvulnerablity(u32 battler, u32 move) return FALSE; } + +bool32 HasPartnerTrainer(u32 battler) +{ + if ((GetBattlerSide(battler) == B_SIDE_PLAYER && gBattleTypeFlags & BATTLE_TYPE_PLAYER_HAS_PARTNER) + || (GetBattlerSide(battler) == B_SIDE_OPPONENT && gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS)) + return TRUE; + else + return FALSE; +} From 4a714225e526a7af3bbb815a9e0553cea2f9700e Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:25:05 +0000 Subject: [PATCH 11/12] Fixes hazards and switch-in items not being reset when switching in (#8074) --- src/battle_main.c | 3 +++ src/battle_script_commands.c | 1 - test/battle/hazards.c | 23 +++++++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/battle_main.c b/src/battle_main.c index 6aa4c7212d..99464151a8 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3235,6 +3235,9 @@ void SwitchInClearSetData(u32 battler, struct Volatiles *volatilesCopy) gBattleStruct->battlerState[battler].stompingTantrumTimer = 0; gBattleStruct->palaceFlags &= ~(1u << battler); gBattleStruct->battlerState[battler].canPickupItem = FALSE; + gBattleStruct->hazardsCounter = 0; + gDisableStructs[battler].hazardsDone = FALSE; + gSpecialStatuses[battler].switchInItemDone = FALSE; ClearPursuitValuesIfSet(battler); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 223a4bd382..6d9ba3d61e 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -7891,7 +7891,6 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) gBattleStruct->hpOnSwitchout[GetBattlerSide(i)] = gBattleMons[i].hp; } - gDisableStructs[battler].hazardsDone = FALSE; gBattleStruct->battlerState[battler].forcedSwitch = FALSE; return FALSE; } diff --git a/test/battle/hazards.c b/test/battle/hazards.c index 9c974bb7b1..17ecb41f5c 100644 --- a/test/battle/hazards.c +++ b/test/battle/hazards.c @@ -38,3 +38,26 @@ SINGLE_BATTLE_TEST("Hazards are applied based on order of set up") EXPECT_EQ(gBattleStruct->hazardsQueue[0][5], HAZARDS_NONE); } } + +SINGLE_BATTLE_TEST("Hazards are applied correctly after a battler faints") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FINAL_GAMBIT) == EFFECT_FINAL_GAMBIT); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_STEALTH_ROCK); + MOVE(player, MOVE_FINAL_GAMBIT); + SEND_OUT(player, 1); + SEND_OUT(player, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FINAL_GAMBIT, player); + MESSAGE("Wynaut fainted!"); + MESSAGE("Pointed stones dug into Wobbuffet!"); + MESSAGE("Wobbuffet fainted!"); + MESSAGE("Pointed stones dug into Wynaut!"); + } +} From ea60c32fbeb446a7a8b61c65da708d8a9bc945f6 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:19:50 +0100 Subject: [PATCH 12/12] Fixes Liquid Ooze dmg not blocked by Magic Guard (#8036) Co-authored-by: Bassoonian --- data/battle_scripts_1.s | 4 ++++ test/battle/ability/liquid_ooze.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 36c3712854..d65e846880 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -2880,6 +2880,7 @@ BattleScript_CantMakeAsleep:: BattleScript_EffectAbsorbLiquidOoze:: call BattleScript_AbilityPopUpTarget + jumpifability BS_ATTACKER, ABILITY_MAGIC_GUARD, BattleScript_EffectAbsorbRet goto BattleScript_EffectAbsorb BattleScript_EffectAbsorb:: @@ -2889,6 +2890,7 @@ BattleScript_EffectAbsorb:: waitmessage B_WAIT_TIME_LONG tryfaintmon BS_ATTACKER bicword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_PASSIVE_HP_UPDATE | HITMARKER_PASSIVE_HP_UPDATE +BattleScript_EffectAbsorbRet: return BattleScript_EffectExplosion:: @@ -5625,10 +5627,12 @@ BattleScript_LeechSeedTurnDrainLiquidOoze:: copybyte gBattlerAbility, gBattlerAttacker call BattleScript_AbilityPopUp copybyte gBattlerAttacker, gBattlerTarget @ needed to get liquid ooze message correct + jumpifability BS_TARGET, ABILITY_MAGIC_GUARD, BattleScript_LeechSeedTurnDrainHealBlockEnd2 goto BattleScript_LeechSeedTurnDrainGainHp BattleScript_LeechSeedTurnDrainHealBlock:: call BattleScript_LeechSeedTurnDrain +BattleScript_LeechSeedTurnDrainHealBlockEnd2: end2 BattleScript_LeechSeedTurnDrainRecovery:: diff --git a/test/battle/ability/liquid_ooze.c b/test/battle/ability/liquid_ooze.c index 2c89702911..ca7c88ebdd 100644 --- a/test/battle/ability/liquid_ooze.c +++ b/test/battle/ability/liquid_ooze.c @@ -187,3 +187,34 @@ SINGLE_BATTLE_TEST("Liquid Ooze does not cause Dream Eater users to lose HP inst EXPECT_LT(damage, 0); // Negative damage = Heal } } + +SINGLE_BATTLE_TEST("Liquid Ooze HP loss from Absorb is blocked by Magic Guard") +{ + GIVEN { + PLAYER(SPECIES_CLEFFA) { Ability(ABILITY_MAGIC_GUARD); } + OPPONENT(SPECIES_TENTACOOL) { Ability(ABILITY_LIQUID_OOZE); } + } WHEN { + TURN { MOVE(player, MOVE_ABSORB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player); + HP_BAR(opponent); + NONE_OF { + HP_BAR(player); + MESSAGE("Wobbuffet sucked up the liquid ooze!"); + } + } +} + +SINGLE_BATTLE_TEST("Liquid Ooze HP loss from Leech Seed is blocked by Magic Guard") +{ + GIVEN { + PLAYER(SPECIES_CLEFFA) { Ability(ABILITY_MAGIC_GUARD); } + OPPONENT(SPECIES_TENTACOOL) { Ability(ABILITY_LIQUID_OOZE); } + } WHEN { + TURN { MOVE(player, MOVE_LEECH_SEED); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LEECH_SEED, player); + HP_BAR(opponent); + NOT HP_BAR(player); + } +}