From c5f788444a919e5b46135b69f0f4e3a83fc853eb Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:49:16 -0400 Subject: [PATCH 01/17] Fix choice AI not seeing Gorilla Tactics (#7316) --- src/battle_ai_switch_items.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 7695a0a154..9138f59d59 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -160,10 +160,12 @@ static inline bool32 SetSwitchinAndSwitch(u32 battler, u32 switchinId) return TRUE; } -static bool32 AI_DoesChoiceItemBlockMove(u32 battler, u32 move) +static bool32 AI_DoesChoiceEffectBlockMove(u32 battler, u32 move) { // Choice locked into something else - if (gAiLogicData->lastUsedMove[battler] != MOVE_NONE && gAiLogicData->lastUsedMove[battler] != move && HOLD_EFFECT_CHOICE(GetBattlerHoldEffect(battler, FALSE)) && IsBattlerItemEnabled(battler)) + if (gAiLogicData->lastUsedMove[battler] != MOVE_NONE && gAiLogicData->lastUsedMove[battler] != move + && ((HOLD_EFFECT_CHOICE(GetBattlerHoldEffect(battler, FALSE)) && IsBattlerItemEnabled(battler)) + || gBattleMons[battler].ability == ABILITY_GORILLA_TACTICS)) return TRUE; return FALSE; } @@ -225,12 +227,12 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) if (!IsBattleMoveStatus(aiMove)) { // Check if mon has a super effective move - if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) >= UQ_4_12(2.0) && !AI_DoesChoiceItemBlockMove(battler, aiMove)) + if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) >= UQ_4_12(2.0) && !AI_DoesChoiceEffectBlockMove(battler, aiMove)) hasSuperEffectiveMove = TRUE; // Get maximum damage mon can deal damageDealt = AI_GetDamage(battler, opposingBattler, i, AI_ATTACKING, gAiLogicData); - if (damageDealt > maxDamageDealt && !AI_DoesChoiceItemBlockMove(battler, aiMove)) + if (damageDealt > maxDamageDealt && !AI_DoesChoiceEffectBlockMove(battler, aiMove)) { maxDamageDealt = damageDealt; aiBestMove = aiMove; @@ -483,7 +485,7 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) // Only check damage if it's a damaging move if (!IsBattleMoveStatus(aiMove)) { - if (!AI_DoesChoiceItemBlockMove(battler, aiMove) && AI_GetDamage(battler, opposingBattler, i, AI_ATTACKING, gAiLogicData) > gBattleMons[opposingBattler].hp) + if (!AI_DoesChoiceEffectBlockMove(battler, aiMove) && AI_GetDamage(battler, opposingBattler, i, AI_ATTACKING, gAiLogicData) > gBattleMons[opposingBattler].hp) return FALSE; } } @@ -802,7 +804,7 @@ static bool32 CanUseSuperEffectiveMoveAgainstOpponents(u32 battler) for (i = 0; i < MAX_MON_MOVES; i++) { move = gBattleMons[battler].moves[i]; - if (move == MOVE_NONE || AI_DoesChoiceItemBlockMove(battler, move)) + if (move == MOVE_NONE || AI_DoesChoiceEffectBlockMove(battler, move)) continue; if (AI_GetMoveEffectiveness(move, battler, opposingBattler) >= UQ_4_12(2.0)) @@ -821,7 +823,7 @@ static bool32 CanUseSuperEffectiveMoveAgainstOpponents(u32 battler) for (i = 0; i < MAX_MON_MOVES; i++) { move = gBattleMons[battler].moves[i]; - if (move == MOVE_NONE || AI_DoesChoiceItemBlockMove(battler, move)) + if (move == MOVE_NONE || AI_DoesChoiceEffectBlockMove(battler, move)) continue; if (AI_GetMoveEffectiveness(move, battler, opposingBattler) >= UQ_4_12(2.0)) From 809379797e3e14a046a8a00309e421bc87f29ac8 Mon Sep 17 00:00:00 2001 From: Phantonomy <131238004+Ddaretrogamer@users.noreply.github.com> Date: Thu, 10 Jul 2025 22:43:43 -0500 Subject: [PATCH 02/17] Better Hydrapple Backsprite (Adapted from DegelAlex on deviantart (#7246) --- graphics/pokemon/hydrapple/back.png | Bin 786 -> 907 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/graphics/pokemon/hydrapple/back.png b/graphics/pokemon/hydrapple/back.png index 19ffd6d199fec84043f0f1be9d8a933cbc9613f7..ec5f77cabe3b754655835fa4aae16c6f868992de 100644 GIT binary patch delta 837 zcmV-L1G@Z@28#!f7-Iwj00013M{Ml?000McNliru=?4lIFA~_!AWi@P02g#cSaefw zW^{L9a%BKVOhiylM<8}(av(DOV1ZP1_ zK>z@;j|==^1(7a3e*ghuOGiWihy@);00087Nkl=tqeC8svfC6_)#0vr4azBIU~m!a-E7si&yiF=AZc`&-)J-8Hxq?69xl7^#ovpVKoF)?J+x+e;=#6TcUQ?@Zm*%Jt*Qs zp69niBA(^>_3e-dlIP!oAra*7-6|LqL045ZJP8`AU`Pa3sU`Ux?T-7L(ou)4@g}cv<|{P04o4U4p8dF&pr)4 z9v~CNf$snqe*#HG6<29f^hsbig9^=3D%DXhP=dshiBjcas{+0W!r&gL7dQUK8LaWf z2oz>NRCTe!jV>TtpfQf}_|Mlaz`2N+K!EFrn}LUuP6@7c6R`pTEhNVAqcce#P1FsG z4Uh^uZ`m;`H702X$P(jRz&LvB13j=wro>VjKU59Sf20pGeb6>ZA|(X+fwM%x=3=>p ze}I}K=D2tfDvF!yK;cm(I0C368B0RF5r<$6Ks|J%EFcv#sYJzI2Ov;TGfHgGrP2}i z+CG>Aq_P0S_AdB%|9H~{D&c^josDj~0K~+VcpUCBcmnwK$pXZ*Y)aq!xN zfRaHHe^_8S5^z{N0(fQC0G$4U)Pl8+SrHha2*mbY`lyDeT1w|w7l7CTc>imBW3sJb z+L%rdcxzP(1Ca3EG1H#6H=TV(;2o1H!1rn9O&%O$Q0%?R=Ep|>ZzhmD2a~bx7>d|G z&$6r8@$}>ZlKs;b@Mvo}*7)%;bL&E?gD(IZS8tEO_corPhJ!h5zHS0X=j{R5W_C5% zdc5?+0rOKFd6vE0jFZ`H=1zDG$w8K#^#P8!c0004VQb$4nuFf3kk$XOW0+2~WK~z|U#g++f z#4rd&gM>N?vXlGYwtzRhr!%b@Rch7bKYy?RAIEP#z|V&Sbjo-i1dLz?o+sib2xP?d zV**RTBHkb%>j3UMBH$eloPqm3PyI`5WVZg=Cq@l0;kp*%(L(@afUeD2mU==V;el+-tqJ^N^|C2K5CjJyfOD5e z@!(uCKtvJYK`;#19*;=i^mFJeVgza&l1NU?)l2{XOMj7<5DzBMy!jh|6mrzNBuIq0-k#ztS3)Qg zO~hjO^Z;v=!nl;hVzqsk= z$u%O35w>>WD(UJ7!DNp>Q#d>|^eTcY#Lz&_l#Jki((qfvh)@i~(Zg!f@f(EihXLm9 znI{KdN6a!cqO+g_ChE)ls~aY)EI^7tE>s|hFg}_vD?$ed`Xe-)G(HvE>xu7nI@f7|5P#?uaSXr?8IbcDk3{YDPIfxkn yDg&J>zU(pL@C<<|5p}RUs4b$E)AC~SFV8nMEIw<{+}~FK0000 Date: Fri, 11 Jul 2025 03:43:14 -0400 Subject: [PATCH 03/17] Cotton down use jumpifabsent (#7318) Co-authored-by: ghoulslash --- data/battle_scripts_1.s | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 60432842f9..a4d3264d13 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -6913,7 +6913,7 @@ BattleScript_CottonDownActivates:: swapattackerwithtarget setbyte gBattlerTarget, 0 BattleScript_CottonDownLoop: - jumpiffainted BS_TARGET, TRUE, BattleScript_CottonDownLoopIncrement + jumpifabsent BS_TARGET, BattleScript_CottonDownLoopIncrement setstatchanger STAT_SPEED, 1, TRUE jumpifbyteequal gBattlerTarget, gEffectBattler, BattleScript_CottonDownLoopIncrement statbuffchange STAT_CHANGE_NOT_PROTECT_AFFECTED | STAT_CHANGE_ALLOW_PTR, BattleScript_CottonDownLoopIncrement From d63f859db6d7a7c6b2a7a68e7f1e32db1dc6af99 Mon Sep 17 00:00:00 2001 From: grintoul <166724814+grintoul1@users.noreply.github.com> Date: Fri, 11 Jul 2025 08:53:03 +0100 Subject: [PATCH 04/17] Multibattles Illusion party boundaries fix (#7308) --- include/battle_util.h | 2 +- src/battle_message.c | 2 +- src/battle_util.c | 26 ++++++++++++++++++++++---- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/include/battle_util.h b/include/battle_util.h index 57327176a8..ed67d21c3f 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -297,7 +297,7 @@ bool32 TryClearIllusion(u32 battler, u32 caseID); u32 GetIllusionMonSpecies(u32 battler); struct Pokemon *GetIllusionMonPtr(u32 battler); void ClearIllusionMon(u32 battler); -u32 GetIllusionMonPartyId(struct Pokemon *party, struct Pokemon *mon, struct Pokemon *partnerMon); +u32 GetIllusionMonPartyId(struct Pokemon *party, struct Pokemon *mon, struct Pokemon *partnerMon, u32 battler); bool32 SetIllusionMon(struct Pokemon *mon, u32 battler); bool32 ShouldGetStatBadgeBoost(u16 flagId, u32 battler); u32 GetBattleMoveCategory(u32 move); diff --git a/src/battle_message.c b/src/battle_message.c index 78708d47d6..350cddd374 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -3187,7 +3187,7 @@ static void IllusionNickHack(u32 battler, u32 partyId, u8 *dst) else partnerMon = mon; - id = GetIllusionMonPartyId(gEnemyParty, mon, partnerMon); + id = GetIllusionMonPartyId(gEnemyParty, mon, partnerMon, battler); } if (id != PARTY_SIZE) diff --git a/src/battle_util.c b/src/battle_util.c index 4f0ffcf962..a544d1484d 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -10373,11 +10373,29 @@ u32 GetIllusionMonSpecies(u32 battler) return SPECIES_NONE; } -u32 GetIllusionMonPartyId(struct Pokemon *party, struct Pokemon *mon, struct Pokemon *partnerMon) +u32 GetIllusionMonPartyId(struct Pokemon *party, struct Pokemon *mon, struct Pokemon *partnerMon, u32 battler) { - s32 id; + s32 partyEnd=6; + s32 partyStart=0; + + // Adjust party search range for Multibattles and Player vs two-trainers + if((GetBattlerSide(battler) == B_SIDE_PLAYER && (gBattleTypeFlags & BATTLE_TYPE_MULTI)) + || (GetBattlerSide(battler) == B_SIDE_OPPONENT && (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS))) + { + if((GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT) || (GetBattlerPosition(battler) == B_POSITION_OPPONENT_LEFT)) + { + partyEnd = 3; + partyStart = 0; + } + else + { + partyEnd = 6; + partyStart = 3; + } + } + // Find last alive non-egg pokemon. - for (id = PARTY_SIZE - 1; id >= 0; id--) + for (s32 id = partyEnd - 1; id >= partyStart; id--) { if (GetMonData(&party[id], MON_DATA_SANITY_HAS_SPECIES) && GetMonData(&party[id], MON_DATA_HP) @@ -10411,7 +10429,7 @@ bool32 SetIllusionMon(struct Pokemon *mon, u32 battler) else partnerMon = mon; - id = GetIllusionMonPartyId(party, mon, partnerMon); + id = GetIllusionMonPartyId(party, mon, partnerMon, battler); if (id != PARTY_SIZE) { gBattleStruct->illusion[battler].state = ILLUSION_ON; From b55ee2288b66ccc4614026a9e129ab17f2025ccc Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Sat, 12 Jul 2025 15:28:05 -0400 Subject: [PATCH 05/17] Fix AI seeing switchin abilities wrong in tests (#7328) --- src/battle_controllers.c | 2 +- src/battle_main.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/battle_controllers.c b/src/battle_controllers.c index ec3dfa7426..d189c67215 100644 --- a/src/battle_controllers.c +++ b/src/battle_controllers.c @@ -1652,7 +1652,7 @@ static u32 GetBattlerMonData(u32 battler, struct Pokemon *party, u32 monId, u8 * u32 side = GetBattlerSide(battler); u32 partyIndex = gBattlerPartyIndexes[battler]; if (TestRunner_Battle_GetForcedAbility(side, partyIndex)) - gBattleMons[battler].ability = gDisableStructs[battler].overwrittenAbility = TestRunner_Battle_GetForcedAbility(side, partyIndex); + gBattleMons[battler].ability = TestRunner_Battle_GetForcedAbility(side, partyIndex); } #endif break; diff --git a/src/battle_main.c b/src/battle_main.c index fc7304b1a3..8bab8fd1ef 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3263,7 +3263,7 @@ void SwitchInClearSetData(u32 battler) u32 side = GetBattlerSide(battler); u32 partyIndex = gBattlerPartyIndexes[battler]; if (TestRunner_Battle_GetForcedAbility(side, partyIndex)) - gBattleMons[i].ability = gDisableStructs[i].overwrittenAbility = TestRunner_Battle_GetForcedAbility(side, partyIndex); + gBattleMons[i].ability = TestRunner_Battle_GetForcedAbility(side, partyIndex); } #endif // TESTING @@ -3474,7 +3474,7 @@ static void DoBattleIntro(void) u32 side = GetBattlerSide(battler); u32 partyIndex = gBattlerPartyIndexes[battler]; if (TestRunner_Battle_GetForcedAbility(side, partyIndex)) - gBattleMons[battler].ability = gDisableStructs[battler].overwrittenAbility = TestRunner_Battle_GetForcedAbility(side, partyIndex); + gBattleMons[battler].ability = TestRunner_Battle_GetForcedAbility(side, partyIndex); } #endif } @@ -3770,7 +3770,7 @@ static void TryDoEventsBeforeFirstTurn(void) u32 side = GetBattlerSide(i); u32 partyIndex = gBattlerPartyIndexes[i]; if (TestRunner_Battle_GetForcedAbility(side, partyIndex)) - gBattleMons[i].ability = gDisableStructs[i].overwrittenAbility = TestRunner_Battle_GetForcedAbility(side, partyIndex); + gBattleMons[i].ability = TestRunner_Battle_GetForcedAbility(side, partyIndex); } } #endif // TESTING From 06c54a2d607dfe0223aca6f0af2773e62bf157e2 Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Sun, 13 Jul 2025 11:45:59 -0400 Subject: [PATCH 06/17] Fix switching 1v1 calcs not handling 0 (#7131) --- src/battle_ai_switch_items.c | 34 +++++++++++++++++++++++----------- test/battle/ai/ai_switching.c | 13 +++++++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 9138f59d59..2ca97a5720 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -261,13 +261,10 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) if (playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH) { damageTaken = AI_GetDamage(opposingBattler, battler, i, AI_DEFENDING, gAiLogicData); - if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move + if (damageTaken > maxDamageTaken && !AI_DoesChoiceItemBlockMove(opposingBattler, playerMove)) { - return maxDamageTaken = damageTaken; - break; - } - if (damageTaken > maxDamageTaken) maxDamageTaken = damageTaken; + } } } @@ -281,7 +278,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) } // Check if current mon can 1v1 in spite of bad matchup, and don't switch out if it can - if (hitsToKoPlayer < hitsToKoAI || (hitsToKoPlayer == hitsToKoAI && AI_IsFaster(battler, opposingBattler, aiBestMove))) + if ((hitsToKoPlayer != 0 && (hitsToKoPlayer < hitsToKoAI || hitsToKoAI == 0)) || (hitsToKoPlayer == hitsToKoAI && AI_IsFaster(battler, opposingBattler, aiBestMove))) return FALSE; // If we don't have any other viable options, don't switch out @@ -1805,7 +1802,7 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, u32 battler) // No damage being dealt if ((damageTaken + statusDamage + recurringDamage <= recurringHealing) || damageTaken + statusDamage + recurringDamage == 0) - return startingHP; + return hitsToKO; // Mon fainted to hazards if (startingHP == 0) @@ -2012,6 +2009,18 @@ static inline bool32 IsFreeSwitch(enum SwitchType switchType, u32 battlerSwitchi static inline bool32 CanSwitchinWin1v1(u32 hitsToKOAI, u32 hitsToKOPlayer, bool32 isSwitchinFirst, bool32 isFreeSwitch) { + // Player's best move deals 0 damage + if (hitsToKOAI == 0 && hitsToKOPlayer > 0) + return TRUE; + + // AI's best move deals 0 damage + if (hitsToKOPlayer == 0 && hitsToKOAI > 0) + return FALSE; + + // Neither mon can damage the other + if (hitsToKOPlayer == 0 && hitsToKOAI == 0) + return FALSE; + // Free switch, need to outspeed or take 1 extra hit if (isFreeSwitch) { @@ -2028,7 +2037,7 @@ static inline bool32 CanSwitchinWin1v1(u32 hitsToKOAI, u32 hitsToKOPlayer, bool3 // Everything runs in the same loop to minimize computation time. This makes it harder to read, but hopefully the comments can guide you! static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, u32 battler, u32 opposingBattler, u32 battlerIn1, u32 battlerIn2, enum SwitchType switchType) { - int revengeKillerId = PARTY_SIZE, slowRevengeKillerId = PARTY_SIZE, fastThreatenId = PARTY_SIZE, slowThreatenId = PARTY_SIZE, damageMonId = PARTY_SIZE; + int revengeKillerId = PARTY_SIZE, slowRevengeKillerId = PARTY_SIZE, fastThreatenId = PARTY_SIZE, slowThreatenId = PARTY_SIZE, damageMonId = PARTY_SIZE, generic1v1MonId = PARTY_SIZE; int batonPassId = PARTY_SIZE, typeMatchupId = PARTY_SIZE, typeMatchupEffectiveId = PARTY_SIZE, defensiveMonId = PARTY_SIZE, aceMonId = PARTY_SIZE, trapperId = PARTY_SIZE; int i, j, aliveCount = 0, bits = 0, aceMonCount = 0; s32 defensiveMonHitKOThreshold = 3; // 3HKO threshold that candidate defensive mons must exceed @@ -2073,9 +2082,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, for (j = 0; j < MAX_MON_MOVES; j++) { aiMove = gAiLogicData->switchinCandidate.battleMon.moves[j]; - - if (aiMove != MOVE_NONE && !IsBattleMoveStatus(aiMove)) - damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, AI_ATTACKING); + damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, AI_ATTACKING); // Offensive switchin decisions are based on which whether switchin moves first and whether it can win a 1v1 isSwitchinFirst = AI_WhoStrikesFirstPartyMon(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, aiMove); @@ -2106,6 +2113,9 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, defensiveMonId = i; } + if (canSwitchinWin1v1) + generic1v1MonId = i; + // Check for mon with resistance and super effective move for best type matchup mon with effective move if (aiMove != MOVE_NONE && !IsBattleMoveStatus(aiMove)) { @@ -2184,6 +2194,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, else if (typeMatchupEffectiveId != PARTY_SIZE) return typeMatchupEffectiveId; else if (typeMatchupId != PARTY_SIZE) return typeMatchupId; else if (batonPassId != PARTY_SIZE) return batonPassId; + else if (generic1v1MonId != PARTY_SIZE) return generic1v1MonId; else if (damageMonId != PARTY_SIZE) return damageMonId; } else @@ -2194,6 +2205,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, else if (typeMatchupId != PARTY_SIZE) return typeMatchupId; else if (defensiveMonId != PARTY_SIZE) return defensiveMonId; else if (batonPassId != PARTY_SIZE) return batonPassId; + else if (generic1v1MonId != PARTY_SIZE) return generic1v1MonId; } // If ace mon is the last available Pokemon and U-Turn/Volt Switch or Eject Pack/Button was used - switch to the mon. if (aceMonId != PARTY_SIZE && CountUsablePartyMons(battler) <= aceMonCount diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index e9ad61bdfa..0c9e94695e 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -1255,3 +1255,16 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI won't send out defensive mon TURN { MOVE(player, MOVE_WATER_PULSE); EXPECT_MOVE(opponent, MOVE_BULLDOZE); } } } + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI considers 0 hits to KO as losing a 1v1") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_JOLTEON) { Level(100); Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_ZIGZAGOON) { Level(1); HP(1); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_TANGELA) { Level(100); Moves(MOVE_THUNDERBOLT); } + OPPONENT(SPECIES_TANGELA) { Level(100); Moves(MOVE_GIGA_DRAIN); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); EXPECT_SEND_OUT(opponent, 2); } + } +} From 939e168c041dc8499b50671a7b8a1645627b8402 Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Sun, 13 Jul 2025 13:24:14 -0400 Subject: [PATCH 07/17] Fix AI_DoesChoiceEffectBlockMove typo (#7335) --- src/battle_ai_switch_items.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 2ca97a5720..c044ff2804 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -261,7 +261,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) if (playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH) { damageTaken = AI_GetDamage(opposingBattler, battler, i, AI_DEFENDING, gAiLogicData); - if (damageTaken > maxDamageTaken && !AI_DoesChoiceItemBlockMove(opposingBattler, playerMove)) + if (damageTaken > maxDamageTaken && !AI_DoesChoiceEffectBlockMove(opposingBattler, playerMove)) { maxDamageTaken = damageTaken; } From 73a2e5104e97f973ff0b2b4e6eb76340e83bd2dd Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Mon, 14 Jul 2025 21:34:21 +0200 Subject: [PATCH 08/17] Bugfixes for Knock Off, Cover, Thief, Ceaseless Edge and Stone Axe (#7326) --- include/battle.h | 3 +- include/constants/battle.h | 13 +- include/constants/battle_move_effects.h | 3 + include/constants/battle_script_commands.h | 3 +- src/battle_ai_main.c | 147 +++-- src/battle_script_commands.c | 528 +++++++++--------- src/battle_tv.c | 4 - src/battle_util.c | 16 + src/data/battle_move_effects.h | 18 + src/data/moves_info.h | 20 +- test/battle/ability/sheer_force.c | 1 - test/battle/ability/sticky_hold.c | 2 +- test/battle/hold_effect/air_balloon.c | 9 +- test/battle/hold_effect/red_card.c | 2 +- test/battle/hold_effect/restore_stats.c | 7 +- .../spikes.c => move_effect/ceaseless_edge.c} | 18 +- test/battle/move_effect/hit_switch_target.c | 4 +- test/battle/move_effect/ice_spinner.c | 5 +- test/battle/move_effect/knock_off.c | 58 +- test/battle/move_effect/stone_axe.c | 94 ++++ test/battle/move_effect_secondary/pay_day.c | 2 +- .../battle/move_effect_secondary/steal_item.c | 42 +- .../move_effect_secondary/stealth_rock.c | 58 +- .../move_effects_combined/make_it_rain.c | 2 +- 24 files changed, 601 insertions(+), 458 deletions(-) rename test/battle/{move_effect_secondary/spikes.c => move_effect/ceaseless_edge.c} (76%) create mode 100644 test/battle/move_effect/stone_axe.c diff --git a/include/battle.h b/include/battle.h index 6b9609758b..d59432e6c1 100644 --- a/include/battle.h +++ b/include/battle.h @@ -588,7 +588,8 @@ struct BattlerState u32 pursuitTarget:1; u32 stompingTantrumTimer:2; u32 canPickupItem:1; - u32 padding:16; + u32 itemCanBeKnockedOff:1; + u32 padding:15; // End of Word }; diff --git a/include/constants/battle.h b/include/constants/battle.h index 39f2dbd236..bc7cdc1069 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -363,7 +363,6 @@ enum MoveEffects MOVE_EFFECT_REMOVE_ARG_TYPE, MOVE_EFFECT_RECHARGE, MOVE_EFFECT_RAGE, - MOVE_EFFECT_STEAL_ITEM, MOVE_EFFECT_PREVENT_ESCAPE, MOVE_EFFECT_NIGHTMARE, MOVE_EFFECT_ALL_STATS_UP, @@ -399,8 +398,6 @@ enum MoveEffects MOVE_EFFECT_TRAP_BOTH, MOVE_EFFECT_ROUND, MOVE_EFFECT_DIRE_CLAW, - MOVE_EFFECT_STEALTH_ROCK, - MOVE_EFFECT_SPIKES, MOVE_EFFECT_SYRUP_BOMB, MOVE_EFFECT_FLORAL_HEALING, MOVE_EFFECT_SECRET_POWER, @@ -414,6 +411,11 @@ enum MoveEffects MOVE_EFFECT_LIGHT_SCREEN, MOVE_EFFECT_SALT_CURE, MOVE_EFFECT_EERIE_SPELL, + + // Max move effects happen earlier in the execution chain. + // For example stealth rock from G-Max Stonesurge is set up before abilities but from Stone Axe after. + // Stone Axe can also fail to set up rocks if user faints where as Stonesurge will always go up. + // This means we need to be careful if we want to re-use those effects for (new) vanilla moves MOVE_EFFECT_RAISE_TEAM_ATTACK, MOVE_EFFECT_RAISE_TEAM_DEFENSE, MOVE_EFFECT_RAISE_TEAM_SPEED, @@ -455,11 +457,14 @@ enum MoveEffects MOVE_EFFECT_LOWER_EVASIVENESS_SIDE, MOVE_EFFECT_AROMATHERAPY, MOVE_EFFECT_CONFUSE_SIDE, - MOVE_EFFECT_STEELSURGE, + MOVE_EFFECT_STEELSURGE, // Steel type rocks + MOVE_EFFECT_STEALTH_ROCK, // Max Move rocks, not to be confused for rocks set up from Ceasless Edge (same but differ in execution order) MOVE_EFFECT_TORMENT_SIDE, MOVE_EFFECT_LOWER_SPEED_2_SIDE, MOVE_EFFECT_FIRE_SPIN_SIDE, MOVE_EFFECT_FIXED_POWER, + // Max move effects end. They can be used for (custom) normal moves. + NUM_MOVE_EFFECTS }; diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index e21387c2af..97a48929d3 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -155,6 +155,7 @@ enum BattleMoveEffects EFFECT_BRICK_BREAK, EFFECT_YAWN, EFFECT_KNOCK_OFF, + EFFECT_STEAL_ITEM, EFFECT_ENDEAVOR, EFFECT_POWER_BASED_ON_USER_HP, EFFECT_SKILL_SWAP, @@ -351,6 +352,8 @@ enum BattleMoveEffects EFFECT_SMACK_DOWN, EFFECT_ICE_SPINNER, // Removes terrain unless attacker is removed from field either by fainting or ejected out EFFECT_STEEL_ROLLER, // Will fail if there is no terrain up but removes it regardless if attacker is removed from field or not + EFFECT_STONE_AXE, // Not to be confused with MOVE_EFFECT_STEALTH_ROCK. They have two different activation timings. + EFFECT_CEASELESS_EDGE, // Same applies to spikes NUM_BATTLE_MOVE_EFFECTS, }; diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 9e0556d8c5..e5060b4102 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -260,7 +260,6 @@ enum MoveEndEffects MOVEEND_ATTACKER_VISIBLE, MOVEEND_TARGET_VISIBLE, MOVEEND_ITEM_EFFECTS_TARGET, - MOVEEND_FIRST_MOVE_BLOCK, MOVEEND_ITEM_EFFECTS_ALL, MOVEEND_SYMBIOSIS, MOVEEND_KINGSROCK, // These item effects will occur each strike of a multi-hit move @@ -271,7 +270,7 @@ enum MoveEndEffects MOVEEND_DEFROST, MOVEEND_NEXT_TARGET, // Everything up until here is handled for each strike of a spread move MOVEEND_MULTIHIT_MOVE, - MOVEEND_SECOND_MOVE_BLOCK, + MOVEEND_MOVE_BLOCK, MOVEEND_ITEM_EFFECTS_ATTACKER, MOVEEND_ABILITY_BLOCK, MOVEEND_SHEER_FORCE, // If move is Sheer Force affected, skip until Opportunist diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index acf0bdb722..b785180bc2 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -5117,6 +5117,69 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) } } break; + case EFFECT_STEAL_ITEM: + { + bool32 canSteal = FALSE; + + if (B_TRAINERS_KNOCK_OFF_ITEMS == TRUE) + canSteal = TRUE; + + if (gBattleTypeFlags & BATTLE_TYPE_FRONTIER || IsOnPlayerSide(battlerAtk)) + canSteal = TRUE; + + if (canSteal && aiData->items[battlerAtk] == ITEM_NONE + && aiData->items[battlerDef] != ITEM_NONE + && CanBattlerGetOrLoseItem(battlerDef, aiData->items[battlerDef]) + && CanBattlerGetOrLoseItem(battlerAtk, aiData->items[battlerDef]) + && !HasMoveWithEffect(battlerAtk, EFFECT_ACROBATICS) + && aiData->abilities[battlerDef] != ABILITY_STICKY_HOLD) + { + switch (aiData->holdEffects[battlerDef]) + { + case HOLD_EFFECT_NONE: + break; + case HOLD_EFFECT_CHOICE_BAND: + case HOLD_EFFECT_CHOICE_SCARF: + case HOLD_EFFECT_CHOICE_SPECS: + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_TOXIC_ORB: + if (ShouldPoison(battlerAtk, battlerAtk)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_FLAME_ORB: + if (ShouldBurn(battlerAtk, battlerAtk, aiData->abilities[battlerAtk])) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_BLACK_SLUDGE: + if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_IRON_BALL: + if (HasMoveWithEffect(battlerAtk, EFFECT_FLING)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case HOLD_EFFECT_LAGGING_TAIL: + case HOLD_EFFECT_STICKY_BARB: + break; + default: + ADJUST_SCORE(WEAK_EFFECT); + break; + } + } + break; + } + break; + case EFFECT_STONE_AXE: + case EFFECT_CEASELESS_EDGE: + if (AI_ShouldSetUpHazards(battlerAtk, battlerDef, aiData)); + { + if (gDisableStructs[battlerAtk].isFirstTurn) + ADJUST_SCORE(BEST_EFFECT); + else + ADJUST_SCORE(DECENT_EFFECT); + } + break; default: break; } // move effect checks @@ -5256,68 +5319,6 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) else if (GetItemPocket(aiData->items[battlerDef]) == POCKET_BERRIES || aiData->holdEffects[battlerDef] == HOLD_EFFECT_GEMS) ADJUST_SCORE(DECENT_EFFECT); break; - case MOVE_EFFECT_STEAL_ITEM: - { - bool32 canSteal = FALSE; - - if (B_TRAINERS_KNOCK_OFF_ITEMS == TRUE) - canSteal = TRUE; - if (gBattleTypeFlags & BATTLE_TYPE_FRONTIER || IsOnPlayerSide(battlerAtk)) - canSteal = TRUE; - - if (canSteal && aiData->items[battlerAtk] == ITEM_NONE - && aiData->items[battlerDef] != ITEM_NONE - && CanBattlerGetOrLoseItem(battlerDef, aiData->items[battlerDef]) - && CanBattlerGetOrLoseItem(battlerAtk, aiData->items[battlerDef]) - && !HasMoveWithEffect(battlerAtk, EFFECT_ACROBATICS) - && aiData->abilities[battlerDef] != ABILITY_STICKY_HOLD) - { - switch (aiData->holdEffects[battlerDef]) - { - case HOLD_EFFECT_NONE: - break; - case HOLD_EFFECT_CHOICE_BAND: - case HOLD_EFFECT_CHOICE_SCARF: - case HOLD_EFFECT_CHOICE_SPECS: - ADJUST_SCORE(DECENT_EFFECT); - break; - case HOLD_EFFECT_TOXIC_ORB: - if (ShouldPoison(battlerAtk, battlerAtk)) - ADJUST_SCORE(DECENT_EFFECT); - break; - case HOLD_EFFECT_FLAME_ORB: - if (ShouldBurn(battlerAtk, battlerAtk, aiData->abilities[battlerAtk])) - ADJUST_SCORE(DECENT_EFFECT); - break; - case HOLD_EFFECT_BLACK_SLUDGE: - if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON)) - ADJUST_SCORE(DECENT_EFFECT); - break; - case HOLD_EFFECT_IRON_BALL: - if (HasMoveWithEffect(battlerAtk, EFFECT_FLING)) - ADJUST_SCORE(DECENT_EFFECT); - break; - case HOLD_EFFECT_LAGGING_TAIL: - case HOLD_EFFECT_STICKY_BARB: - break; - default: - ADJUST_SCORE(WEAK_EFFECT); - break; - } - } - break; - } - break; - case MOVE_EFFECT_STEALTH_ROCK: - case MOVE_EFFECT_SPIKES: - if (AI_ShouldSetUpHazards(battlerAtk, battlerDef, aiData)); - { - if (gDisableStructs[battlerAtk].isFirstTurn) - ADJUST_SCORE(BEST_EFFECT); - else - ADJUST_SCORE(DECENT_EFFECT); - } - break; case MOVE_EFFECT_FEINT: if (GetMoveEffect(predictedMove) == EFFECT_PROTECT) ADJUST_SCORE(GOOD_EFFECT); @@ -5375,7 +5376,6 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score // Effects that are encouraged on the first turn of battle static s32 AI_ForceSetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { - u8 i; if (IS_TARGETING_PARTNER(battlerAtk, battlerDef) || gBattleResults.battleTurnCounter != 0) return score; @@ -5473,27 +5473,10 @@ static s32 AI_ForceSetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32 case EFFECT_CHILLY_RECEPTION: case EFFECT_GEOMANCY: case EFFECT_VICTORY_DANCE: + case EFFECT_CEASELESS_EDGE: + case EFFECT_STONE_AXE: ADJUST_SCORE(DECENT_EFFECT); break; - case EFFECT_HIT: - { - // TEMPORARY - should applied to all moves regardless of EFFECT - // Consider move effects - u32 additionalEffectCount = GetMoveAdditionalEffectCount(move); - for (i = 0; i < additionalEffectCount; i++) - { - const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(move, i); - switch (additionalEffect->moveEffect) - { - case MOVE_EFFECT_STEALTH_ROCK: - case MOVE_EFFECT_SPIKES: - ADJUST_SCORE(DECENT_EFFECT); - break; - default: - break; - } - } - } default: break; } diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 664fa22f25..cb746ebc99 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -964,8 +964,6 @@ static const u16 sProtectSuccessRates[] = {USHRT_MAX, USHRT_MAX / 2, USHRT_MAX / static const u16 sFinalStrikeOnlyEffects[] = { - MOVE_EFFECT_BUG_BITE, - MOVE_EFFECT_STEAL_ITEM, MOVE_EFFECT_REMOVE_ARG_TYPE, MOVE_EFFECT_REMOVE_STATUS, MOVE_EFFECT_RECOIL_HP_25, @@ -2749,6 +2747,14 @@ static void Cmd_datahpupdate(void) && GetMoveCategory(gCurrentMove) != DAMAGE_CATEGORY_STATUS && IsBattlerTurnDamaged(gBattlerTarget)) gBattleStruct->timesGotHit[GetBattlerSide(gBattlerTarget)][gBattlerPartyIndexes[gBattlerTarget]]++; + + if (GetMoveEffect(gCurrentMove) == EFFECT_KNOCK_OFF + && IsBattlerTurnDamaged(gBattlerTarget) + && gBattleMons[gBattlerTarget].item != 0 + && !DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove) + && CanBattlerGetOrLoseItem(gBattlerTarget, gBattleMons[gBattlerTarget].item) + && !NoAliveMonsForEitherParty()) + gBattleStruct->battlerState[gBattlerTarget].itemCanBeKnockedOff = TRUE; } TryRestoreDamageAfterCheekPouch(battler); @@ -3132,7 +3138,7 @@ void StealTargetItem(u8 battlerStealer, u8 battlerItem) if (B_STEAL_WILD_ITEMS >= GEN_9 && !(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_PALACE)) - && MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_STEAL_ITEM) + && GetMoveEffect(gCurrentMove) == EFFECT_STEAL_ITEM && battlerStealer == gBattlerAttacker) // ensure that Pickpocket isn't activating this { AddBagItem(gLastUsedItem, 1); @@ -3289,10 +3295,8 @@ void SetMoveEffect(bool32 primary, bool32 certain) switch (gBattleScripting.moveEffect) // Set move effects which happen later on { case MOVE_EFFECT_STEALTH_ROCK: - case MOVE_EFFECT_SPIKES: case MOVE_EFFECT_PAYDAY: case MOVE_EFFECT_BUG_BITE: - case MOVE_EFFECT_STEAL_ITEM: activateAfterFaint = TRUE; break; } @@ -3470,6 +3474,26 @@ void SetMoveEffect(bool32 primary, bool32 certain) } gBattlescriptCurrInstr++; break; + case MOVE_EFFECT_BUG_BITE: + if (GetBattlerHoldEffect(gEffectBattler, TRUE) == HOLD_EFFECT_JABOCA_BERRY) + { + // jaboca berry triggers instead of being stolen + gBattlescriptCurrInstr++; + } + else if (GetItemPocket(gBattleMons[gEffectBattler].item) == POCKET_BERRIES + && battlerAbility != ABILITY_STICKY_HOLD) + { + // target loses their berry + gLastUsedItem = gBattleMons[gEffectBattler].item; + gBattleMons[gEffectBattler].item = 0; + CheckSetUnburden(gEffectBattler); + + BtlController_EmitSetMonData(gEffectBattler, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gEffectBattler].item), &gBattleMons[gEffectBattler].item); + MarkBattlerForControllerExec(gEffectBattler); + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_MoveEffectBugBite; + } + break; case MOVE_EFFECT_TRI_ATTACK: if (gBattleMons[gEffectBattler].status1) { @@ -3638,35 +3662,6 @@ void SetMoveEffect(bool32 primary, bool32 certain) gBattleMons[gBattlerAttacker].status2 |= STATUS2_RAGE; gBattlescriptCurrInstr++; break; - case MOVE_EFFECT_STEAL_ITEM: - if (!CanStealItem(gBattlerAttacker, gBattlerTarget, gBattleMons[gBattlerTarget].item) - || gBattleMons[gBattlerAttacker].item != ITEM_NONE - || gBattleMons[gBattlerTarget].item == ITEM_NONE) - { - gBattlescriptCurrInstr++; - } - else if (GetBattlerAbility(gBattlerTarget) == ABILITY_STICKY_HOLD) - { - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_NoItemSteal; - - gLastUsedAbility = gBattleMons[gBattlerTarget].ability; - RecordAbilityBattle(gBattlerTarget, gLastUsedAbility); - } - else - { - StealTargetItem(gBattlerAttacker, gBattlerTarget); // Attacker steals target item - - if (!(B_STEAL_WILD_ITEMS >= GEN_9 - && !(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_PALACE)))) - { - gBattleMons[gBattlerAttacker].item = ITEM_NONE; // Item assigned later on with thief (see MOVEEND_CHANGED_ITEMS) - gBattleStruct->changedItems[gBattlerAttacker] = gLastUsedItem; // Stolen item to be assigned later - } - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_ItemSteal; - } - break; case MOVE_EFFECT_PREVENT_ESCAPE: gBattleMons[gBattlerTarget].status2 |= STATUS2_ESCAPE_PREVENTION; gDisableStructs[gBattlerTarget].battlerPreventingEscape = gBattlerAttacker; @@ -3802,26 +3797,6 @@ void SetMoveEffect(bool32 primary, bool32 certain) gBattlescriptCurrInstr = BattleScript_MoveEffectIncinerate; } break; - case MOVE_EFFECT_BUG_BITE: - if (GetBattlerHoldEffect(gEffectBattler, TRUE) == HOLD_EFFECT_JABOCA_BERRY) - { - // jaboca berry triggers instead of being stolen - gBattlescriptCurrInstr++; - } - else if (GetItemPocket(gBattleMons[gEffectBattler].item) == POCKET_BERRIES - && battlerAbility != ABILITY_STICKY_HOLD) - { - // target loses their berry - gLastUsedItem = gBattleMons[gEffectBattler].item; - gBattleMons[gEffectBattler].item = 0; - CheckSetUnburden(gEffectBattler); - - BtlController_EmitSetMonData(gEffectBattler, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gEffectBattler].item), &gBattleMons[gEffectBattler].item); - MarkBattlerForControllerExec(gEffectBattler); - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_MoveEffectBugBite; - } - break; case MOVE_EFFECT_TRAP_BOTH: if (!(gBattleMons[gBattlerTarget].status2 & STATUS2_ESCAPE_PREVENTION) && !(gBattleMons[gBattlerAttacker].status2 & STATUS2_ESCAPE_PREVENTION)) { @@ -3877,18 +3852,6 @@ void SetMoveEffect(bool32 primary, bool32 certain) gBattlescriptCurrInstr = BattleScript_StealthRockActivates; } break; - case MOVE_EFFECT_SPIKES: - if (gSideTimers[GetBattlerSide(gEffectBattler)].spikesAmount < 3) - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SPIKESSCATTERED; - BattleScriptPush(gBattlescriptCurrInstr + 1); - - if (gBattleStruct->isSkyBattle) - gBattlescriptCurrInstr++; - else - gBattlescriptCurrInstr = BattleScript_SpikesActivates; - } - break; case MOVE_EFFECT_SYRUP_BOMB: if (!(gStatuses4[gEffectBattler] & STATUS4_SYRUP_BOMB)) { @@ -6073,47 +6036,6 @@ static void Cmd_playstatchangeanimation(void) } } -static bool32 TryKnockOffBattleScript(u32 battlerDef) -{ - if (gBattleMons[battlerDef].item != 0 - && CanBattlerGetOrLoseItem(battlerDef, gBattleMons[battlerDef].item) - && !NoAliveMonsForEitherParty()) - { - if (GetBattlerAbility(battlerDef) == ABILITY_STICKY_HOLD && IsBattlerAlive(battlerDef)) - { - gBattlerAbility = battlerDef; - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_StickyHoldActivates; - } - else - { - u32 side = GetBattlerSide(battlerDef); - - gLastUsedItem = gBattleMons[battlerDef].item; - gBattleMons[battlerDef].item = 0; - if (gBattleMons[battlerDef].ability != ABILITY_GORILLA_TACTICS) - gBattleStruct->choicedMove[battlerDef] = 0; - CheckSetUnburden(battlerDef); - - // In Gen 5+, Knock Off removes the target's item rather than rendering it unusable. - if (B_KNOCK_OFF_REMOVAL >= GEN_5) - { - BtlController_EmitSetMonData(battlerDef, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[battlerDef].item), &gBattleMons[battlerDef].item); - MarkBattlerForControllerExec(battlerDef); - } - else - { - gWishFutureKnock.knockedOffMons[side] |= 1u << gBattlerPartyIndexes[battlerDef]; - } - - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_KnockedOff; - } - return TRUE; - } - return FALSE; -} - static inline bool32 TryTriggerSymbiosis(u32 battler, u32 ally) { return GetBattlerAbility(ally) == ABILITY_SYMBIOSIS @@ -6323,6 +6245,233 @@ static bool32 HandleMoveEndAbilityBlock(u32 battlerAtk, u32 battlerDef, u32 move return effect; } +static bool32 HandleMoveEndMoveBlock(u32 moveEffect) +{ + if (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) + return FALSE; + + u32 effect = FALSE; + switch (moveEffect) + { + case EFFECT_KNOCK_OFF: + if (gBattleStruct->battlerState[gBattlerTarget].itemCanBeKnockedOff && IsBattlerAlive(gBattlerAttacker)) + { + u32 side = GetBattlerSide(gBattlerTarget); + gLastUsedItem = gBattleMons[gBattlerTarget].item; + gBattleMons[gBattlerTarget].item = 0; + if (gBattleMons[gBattlerTarget].ability != ABILITY_GORILLA_TACTICS) + gBattleStruct->choicedMove[gBattlerTarget] = 0; + CheckSetUnburden(gBattlerTarget); + + // In Gen 5+, Knock Off removes the target's item rather than rendering it unusable. + if (B_KNOCK_OFF_REMOVAL >= GEN_5) + { + BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].item), &gBattleMons[gBattlerTarget].item); + MarkBattlerForControllerExec(gBattlerTarget); + } + else + { + gWishFutureKnock.knockedOffMons[side] |= 1u << gBattlerPartyIndexes[gBattlerTarget]; + } + + gBattleStruct->battlerState[gBattlerTarget].itemCanBeKnockedOff = FALSE; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_KnockedOff; + effect = TRUE; + } + break; + case EFFECT_STEAL_ITEM: + if (!CanStealItem(gBattlerAttacker, gBattlerTarget, gBattleMons[gBattlerTarget].item) + || gBattleMons[gBattlerAttacker].item != ITEM_NONE + || gBattleMons[gBattlerTarget].item == ITEM_NONE + || !IsBattlerAlive(gBattlerAttacker) + || !IsBattlerTurnDamaged(gBattlerTarget)) + { + effect = FALSE; + } + else if (GetBattlerAbility(gBattlerTarget) == ABILITY_STICKY_HOLD) + { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_NoItemSteal; + + gLastUsedAbility = gBattleMons[gBattlerTarget].ability; + RecordAbilityBattle(gBattlerTarget, gLastUsedAbility); + effect = TRUE; + } + else + { + StealTargetItem(gBattlerAttacker, gBattlerTarget); // Attacker steals target item + + if (!(B_STEAL_WILD_ITEMS >= GEN_9 + && !(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_PALACE)))) + { + gBattleMons[gBattlerAttacker].item = ITEM_NONE; // Item assigned later on with thief (see MOVEEND_CHANGED_ITEMS) + gBattleStruct->changedItems[gBattlerAttacker] = gLastUsedItem; // Stolen item to be assigned later + } + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_ItemSteal; + effect = TRUE; + } + break; + case EFFECT_HIT_SWITCH_TARGET: + if (IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(gBattlerTarget) + && IsBattlerAlive(gBattlerAttacker) + && !(gStatuses3[BATTLE_PARTNER(gBattlerTarget)] & STATUS3_COMMANDER)) + { + u32 targetAbility = GetBattlerAbility(gBattlerTarget); + if (targetAbility == ABILITY_GUARD_DOG) + return FALSE; + + BattleScriptPushCursor(); + if (targetAbility == ABILITY_SUCTION_CUPS) + { + gBattlescriptCurrInstr = BattleScript_AbilityPreventsPhasingOutRet; + } + else if (gStatuses3[gBattlerTarget] & STATUS3_ROOTED) + { + gBattlescriptCurrInstr = BattleScript_PrintMonIsRootedRet; + } + else if (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX) + { + gBattlescriptCurrInstr = BattleScript_HitSwitchTargetDynamaxed; + } + else + { + gBattleScripting.switchCase = B_SWITCH_HIT; + gBattlescriptCurrInstr = BattleScript_TryHitSwitchTarget; + } + effect = TRUE; + } + break; + case EFFECT_SMACK_DOWN: + if (!IsBattlerGrounded(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(gBattlerTarget) + && !DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove)) + { + gStatuses3[gBattlerTarget] |= STATUS3_SMACKED_DOWN; + gStatuses3[gBattlerTarget] &= ~(STATUS3_MAGNET_RISE | STATUS3_TELEKINESIS | STATUS3_ON_AIR); + BattleScriptPush(gBattlescriptCurrInstr); + gBattlescriptCurrInstr = BattleScript_MoveEffectSmackDown; + effect = TRUE; + } + break; + case EFFECT_RECOIL_IF_MISS: + if (IsBattlerAlive(gBattlerAttacker) + && (!IsBattlerTurnDamaged(gBattlerTarget) || gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) + && !gBattleStruct->noTargetPresent) + { + if (B_RECOIL_IF_MISS_DMG >= GEN_5 || (B_CRASH_IF_TARGET_IMMUNE == GEN_4 && gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_DOESNT_AFFECT_FOE)) + gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerAttacker) / 2; + else if (B_RECOIL_IF_MISS_DMG == GEN_4 && (GetNonDynamaxMaxHP(gBattlerTarget) / 2) < gBattleStruct->moveDamage[gBattlerTarget]) + gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerTarget) / 2; + else // Fallback if B_RECOIL_IF_MISS_DMG is set to gen3 or lower. + gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerTarget) / 2; + + if (gBattleStruct->moveDamage[gBattlerAttacker] == 0) + gBattleStruct->moveDamage[gBattlerAttacker] = 1; + + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_RecoilIfMiss; + effect = TRUE; + } + break; + case EFFECT_RECOIL: + if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) + { + gBattleStruct->moveDamage[gBattlerAttacker] = max(1, gBattleStruct->moveDamage[gBattlerTarget] * max(1, GetMoveRecoil(gCurrentMove)) / 100); + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_MoveEffectRecoil; + effect = TRUE; + } + break; + case EFFECT_EXPLOSION: + if (!IsAbilityOnField(ABILITY_DAMP)) + { + gBattleStruct->moveDamage[gBattlerAttacker] = 0; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_FaintAttackerForExplosion; + effect = TRUE; + } + break; + case EFFECT_MAX_HP_50_RECOIL: + case EFFECT_MIND_BLOWN: + if (IsBattlerAlive(gBattlerAttacker) + && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FAILED) + && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD) + { + gBattleStruct->moveDamage[gBattlerAttacker] = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_MaxHp50Recoil; + effect = TRUE; + } + break; + case EFFECT_CHLOROBLAST: + if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) + { + gBattleStruct->moveDamage[gBattlerAttacker] = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_MoveEffectRecoil; + effect = TRUE; + } + break; + case EFFECT_RAPID_SPIN: + if (IsBattlerTurnDamaged(gBattlerTarget)) + { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_RapidSpinAway; + effect = TRUE; + } + break; + case EFFECT_FELL_STINGER: + if (IsBattlerAlive(gBattlerAttacker) + && !IsBattlerAlive(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget) + && !NoAliveMonsForEitherParty() + && CompareStat(gBattlerAttacker, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + SET_STATCHANGER(STAT_ATK, GetGenConfig(GEN_CONFIG_FELL_STINGER_STAT_RAISE) >= GEN_7 ? 3 : 2, FALSE); + PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_ATK); + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_FellStingerRaisesStat; + effect = TRUE; + } + break; + case EFFECT_STONE_AXE: + if (!(gSideStatuses[GetBattlerSide(gBattlerTarget)] & SIDE_STATUS_STEALTH_ROCK) && IsBattlerAlive(gBattlerAttacker)) + { + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_POINTEDSTONESFLOAT; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_StealthRockActivates; + effect = TRUE; + } + break; + case EFFECT_CEASELESS_EDGE: + if (gSideTimers[GetBattlerSide(gBattlerTarget)].spikesAmount < 3 && IsBattlerAlive(gBattlerAttacker)) + { + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SPIKESSCATTERED; + BattleScriptPush(gBattlescriptCurrInstr + 1); + if (gBattleStruct->isSkyBattle) + { + effect = FALSE; + } + else + { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_SpikesActivates; + effect = TRUE; + } + } + break; + default: + effect = FALSE; + break; + } + + return effect; +} + static void Cmd_moveend(void) { CMD_ARGS(u8 endMode, u8 endState); @@ -6587,72 +6736,6 @@ static void Cmd_moveend(void) } gBattleScripting.moveendState++; break; - case MOVEEND_FIRST_MOVE_BLOCK: - if ((gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_1ST_HIT && IsBattlerAlive(gBattlerTarget)) - || gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT - || gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) - { - gBattleScripting.moveendState++; - break; - } - - switch (moveEffect) - { - case EFFECT_KNOCK_OFF: - if (!DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove)) - effect = TryKnockOffBattleScript(gBattlerTarget); - break; - case EFFECT_HIT_SWITCH_TARGET: - if (IsBattlerTurnDamaged(gBattlerTarget) - && IsBattlerAlive(gBattlerTarget) - && IsBattlerAlive(gBattlerAttacker) - && !(gStatuses3[BATTLE_PARTNER(gBattlerTarget)] & STATUS3_COMMANDER)) - { - u32 targetAbility = GetBattlerAbility(gBattlerTarget); - if (targetAbility == ABILITY_GUARD_DOG) - { - gBattleScripting.moveendState++; - break; - } - - effect = TRUE; - BattleScriptPushCursor(); - if (targetAbility == ABILITY_SUCTION_CUPS) - { - gBattlescriptCurrInstr = BattleScript_AbilityPreventsPhasingOutRet; - } - else if (gStatuses3[gBattlerTarget] & STATUS3_ROOTED) - { - gBattlescriptCurrInstr = BattleScript_PrintMonIsRootedRet; - } - else if (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX) - { - gBattlescriptCurrInstr = BattleScript_HitSwitchTargetDynamaxed; - } - else - { - gBattleScripting.switchCase = B_SWITCH_HIT; - gBattlescriptCurrInstr = BattleScript_TryHitSwitchTarget; - } - } - break; - case EFFECT_SMACK_DOWN: - if (!IsBattlerGrounded(gBattlerTarget) - && IsBattlerAlive(gBattlerTarget) - && !DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove)) - { - gStatuses3[gBattlerTarget] |= STATUS3_SMACKED_DOWN; - gStatuses3[gBattlerTarget] &= ~(STATUS3_MAGNET_RISE | STATUS3_TELEKINESIS | STATUS3_ON_AIR); - effect = TRUE; - BattleScriptPush(gBattlescriptCurrInstr); - gBattlescriptCurrInstr = BattleScript_MoveEffectSmackDown; - } - break; - default: - break; - } - gBattleScripting.moveendState++; - break; case MOVEEND_ITEM_EFFECTS_ALL: // item effects for all battlers if (ItemBattleEffects(ITEMEFFECT_MOVE_END, 0, FALSE)) effect = TRUE; @@ -6992,99 +7075,8 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; } - case MOVEEND_SECOND_MOVE_BLOCK: - if (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) - { - gBattleScripting.moveendState++; - break; - } - - switch (moveEffect) - { - case EFFECT_RECOIL_IF_MISS: - if (IsBattlerAlive(gBattlerAttacker) - && (!IsBattlerTurnDamaged(gBattlerTarget) || gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) - && !gBattleStruct->noTargetPresent) - { - if (B_RECOIL_IF_MISS_DMG >= GEN_5 || (B_CRASH_IF_TARGET_IMMUNE == GEN_4 && gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_DOESNT_AFFECT_FOE)) - gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerAttacker) / 2; - else if (B_RECOIL_IF_MISS_DMG == GEN_4 && (GetNonDynamaxMaxHP(gBattlerTarget) / 2) < gBattleStruct->moveDamage[gBattlerTarget]) - gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerTarget) / 2; - else // Fallback if B_RECOIL_IF_MISS_DMG is set to gen3 or lower. - gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerTarget) / 2; - - if (gBattleStruct->moveDamage[gBattlerAttacker] == 0) - gBattleStruct->moveDamage[gBattlerAttacker] = 1; - - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_RecoilIfMiss; - effect = TRUE; - } - break; - case EFFECT_RECOIL: - if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) - { - gBattleStruct->moveDamage[gBattlerAttacker] = max(1, gBattleStruct->moveDamage[gBattlerTarget] * max(1, GetMoveRecoil(gCurrentMove)) / 100); - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_MoveEffectRecoil; - effect = TRUE; - } - break; - case EFFECT_EXPLOSION: - if (!IsAbilityOnField(ABILITY_DAMP)) - { - gBattleStruct->moveDamage[gBattlerAttacker] = 0; - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_FaintAttackerForExplosion; - effect = TRUE; - } - break; - case EFFECT_MAX_HP_50_RECOIL: - case EFFECT_MIND_BLOWN: - if (IsBattlerAlive(gBattlerAttacker) - && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FAILED) - && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD) - { - gBattleStruct->moveDamage[gBattlerAttacker] = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_MaxHp50Recoil; - effect = TRUE; - } - break; - case EFFECT_CHLOROBLAST: - if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) - { - gBattleStruct->moveDamage[gBattlerAttacker] = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_MoveEffectRecoil; - effect = TRUE; - } - break; - case EFFECT_RAPID_SPIN: - if (IsBattlerTurnDamaged(gBattlerTarget)) - { - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_RapidSpinAway; - effect = TRUE; - } - break; - case EFFECT_FELL_STINGER: - if (IsBattlerAlive(gBattlerAttacker) - && !IsBattlerAlive(gBattlerTarget) - && IsBattlerTurnDamaged(gBattlerTarget) - && !NoAliveMonsForEitherParty() - && CompareStat(gBattlerAttacker, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) - { - SET_STATCHANGER(STAT_ATK, GetGenConfig(GEN_CONFIG_FELL_STINGER_STAT_RAISE) >= GEN_7 ? 3 : 2, FALSE); - PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_ATK); - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_FellStingerRaisesStat; - effect = TRUE; - } - break; - default: - break; - } + case MOVEEND_MOVE_BLOCK: + effect = HandleMoveEndMoveBlock(moveEffect); gBattleScripting.moveendState++; break; case MOVEEND_ITEM_EFFECTS_ATTACKER: @@ -7378,7 +7370,9 @@ static void Cmd_moveend(void) switch (moveEffect) { case EFFECT_ICE_SPINNER: - if (IsBattlerAlive(gBattlerAttacker) && IsBattlerTurnDamaged(gBattlerTarget)) + if (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY + && IsBattlerAlive(gBattlerAttacker) + && IsBattlerTurnDamaged(gBattlerTarget)) { BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_RemoveTerrain; diff --git a/src/battle_tv.c b/src/battle_tv.c index 96ce633440..8876127320 100644 --- a/src/battle_tv.c +++ b/src/battle_tv.c @@ -839,10 +839,6 @@ static void AddMovePoints(u8 caseId, u16 arg1, u8 arg2, u8 arg3) const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(move, i); switch (additionalEffect->moveEffect) { - case MOVE_EFFECT_STEAL_ITEM: - if ((additionalEffect->chance == 100 || additionalEffect->chance == 0)) - baseFromEffect += 3; - break; case MOVE_EFFECT_THRASH: if (additionalEffect->self == TRUE) baseFromEffect += 3; diff --git a/src/battle_util.c b/src/battle_util.c index a544d1484d..3e9090516e 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4354,6 +4354,16 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITYEFFECT_MOVE_END: // Think contact abilities. switch (gLastUsedAbility) { + case ABILITY_STICKY_HOLD: + if (gBattleStruct->battlerState[gBattlerTarget].itemCanBeKnockedOff && IsBattlerAlive(gBattlerTarget)) + { + gBattleStruct->battlerState[gBattlerTarget].itemCanBeKnockedOff = FALSE; + gBattlerAbility = gBattlerTarget; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_StickyHoldActivates; + effect++; + } + break; case ABILITY_JUSTIFIED: if (!(gBattleStruct->moveResultFlags[battler] & MOVE_RESULT_NO_EFFECT) && IsBattlerTurnDamaged(gBattlerTarget) @@ -7045,6 +7055,12 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler, bool32 moveTurn) case ITEMEFFECT_MOVE_END: for (battler = 0; battler < gBattlersCount; battler++) { + // If item can be knocked off berry activation will be blocked, but not other items + if (gBattleStruct->battlerState[battler].itemCanBeKnockedOff + && (GetItemPocket(gBattleMons[battler].item) == POCKET_BERRIES + || GetBattlerHoldEffect(battler, TRUE) == HOLD_EFFECT_RESTORE_HP)) + continue; + gLastUsedItem = gBattleMons[battler].item; effect = ItemEffectMoveEnd(battler, GetBattlerHoldEffect(battler, TRUE)); if (effect) diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index 08d8123296..7101bc45fa 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -992,6 +992,12 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = .battleTvScore = 2, }, + [EFFECT_STEAL_ITEM] = + { + .battleScript = BattleScript_EffectHit, + .battleTvScore = 3, + }, + [EFFECT_ENDEAVOR] = { .battleScript = BattleScript_EffectEndeavor, @@ -2230,4 +2236,16 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = .battleScript = BattleScript_EffectSteelRoller, .battleTvScore = 0, // TODO: Assign points }, + + [EFFECT_STONE_AXE] = + { + .battleScript = BattleScript_EffectHit, + .battleTvScore = 0, // TODO: Assign points + }, + + [EFFECT_CEASELESS_EDGE] = + { + .battleScript = BattleScript_EffectHit, + .battleTvScore = 0, // TODO: Assign points + }, }; diff --git a/src/data/moves_info.h b/src/data/moves_info.h index c0e44cf4df..00da8aa15f 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -4481,7 +4481,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .description = COMPOUND_STRING( "While attacking, it may\n" "steal the foe's held item."), - .effect = EFFECT_HIT, + .effect = EFFECT_STEAL_ITEM, .power = B_UPDATED_MOVE_DATA >= GEN_6 ? 60 : 40, .type = TYPE_DARK, .accuracy = 100, @@ -4490,9 +4490,6 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, .makesContact = TRUE, - .additionalEffects = ADDITIONAL_EFFECTS({ - .moveEffect = MOVE_EFFECT_STEAL_ITEM, - }), .ignoresKingsRock = (B_UPDATED_MOVE_FLAGS == GEN_3 || B_UPDATED_MOVE_FLAGS == GEN_4), .meFirstBanned = TRUE, .metronomeBanned = TRUE, @@ -9040,7 +9037,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .description = COMPOUND_STRING( "Cutely begs to obtain an\n" "item held by the foe."), - .effect = EFFECT_HIT, + .effect = EFFECT_STEAL_ITEM, .power = B_UPDATED_MOVE_DATA >= GEN_5 ? 60 : 40, .type = TYPE_NORMAL, .accuracy = 100, @@ -9053,9 +9050,6 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .metronomeBanned = TRUE, .copycatBanned = TRUE, .assistBanned = TRUE, - .additionalEffects = ADDITIONAL_EFFECTS({ - .moveEffect = MOVE_EFFECT_STEAL_ITEM, - }), .contestEffect = CONTEST_EFFECT_APPEAL_AS_GOOD_AS_PREV_ONES, .contestCategory = CONTEST_CATEGORY_CUTE, .contestComboStarterId = 0, @@ -19208,7 +19202,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .description = COMPOUND_STRING( "High critical hit ratio. Sets\n" "Splinters that hurt the foe."), - .effect = EFFECT_HIT, + .effect = EFFECT_STONE_AXE, .power = 65, .type = TYPE_ROCK, .accuracy = 90, @@ -19219,8 +19213,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .makesContact = TRUE, .slicingMove = TRUE, .additionalEffects = ADDITIONAL_EFFECTS({ - .moveEffect = MOVE_EFFECT_STEALTH_ROCK, - .chance = 100, + .sheerForceBoost = SHEER_FORCE_BOOST, }), .battleAnimScript = gBattleAnimMove_StoneAxe, }, @@ -19534,7 +19527,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .description = COMPOUND_STRING( "High critical hit ratio. Sets\n" "Splinters that hurt the foe."), - .effect = EFFECT_HIT, + .effect = EFFECT_CEASELESS_EDGE, .power = 65, .type = TYPE_DARK, .accuracy = 90, @@ -19545,8 +19538,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .makesContact = TRUE, .slicingMove = TRUE, .additionalEffects = ADDITIONAL_EFFECTS({ - .moveEffect = MOVE_EFFECT_SPIKES, - .chance = 100, + .sheerForceBoost = SHEER_FORCE_BOOST, }), .battleAnimScript = gBattleAnimMove_CeaselessEdge, }, diff --git a/test/battle/ability/sheer_force.c b/test/battle/ability/sheer_force.c index ee3e10d14e..076597d49b 100644 --- a/test/battle/ability/sheer_force.c +++ b/test/battle/ability/sheer_force.c @@ -1,7 +1,6 @@ #include "global.h" #include "test/battle.h" - ASSUMPTIONS { ASSUME(MoveIsAffectedBySheerForce(MOVE_ELECTRO_SHOT) == TRUE); diff --git a/test/battle/ability/sticky_hold.c b/test/battle/ability/sticky_hold.c index 708c691c80..d6aee1ee51 100644 --- a/test/battle/ability/sticky_hold.c +++ b/test/battle/ability/sticky_hold.c @@ -4,7 +4,7 @@ SINGLE_BATTLE_TEST("Sticky Hold prevents item theft") { GIVEN { - ASSUME(MoveHasAdditionalEffect(MOVE_THIEF, MOVE_EFFECT_STEAL_ITEM)); + ASSUME(GetMoveEffect(MOVE_THIEF) == EFFECT_STEAL_ITEM); PLAYER(SPECIES_URSALUNA) { Item(ITEM_NONE); } OPPONENT(SPECIES_GASTRODON) { Ability(ABILITY_STICKY_HOLD); Item(ITEM_LIFE_ORB); } } WHEN { diff --git a/test/battle/hold_effect/air_balloon.c b/test/battle/hold_effect/air_balloon.c index 17872d89b8..ad0fb457a8 100644 --- a/test/battle/hold_effect/air_balloon.c +++ b/test/battle/hold_effect/air_balloon.c @@ -102,17 +102,14 @@ SINGLE_BATTLE_TEST("Air Balloon pops before it can be stolen with Magician") } } -SINGLE_BATTLE_TEST("Air Balloon pops before it can be stolen with Thief or Covet") +SINGLE_BATTLE_TEST("Air Balloon pops before it can be stolen by Thief") { - u32 move; - PARAMETRIZE { move = MOVE_THIEF; } - PARAMETRIZE { move = MOVE_COVET; } GIVEN { - ASSUME(MoveHasAdditionalEffect(move, MOVE_EFFECT_STEAL_ITEM) == TRUE); + ASSUME(GetMoveEffect(MOVE_THIEF) == EFFECT_STEAL_ITEM); PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_AIR_BALLOON); }; OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, move); } + TURN { MOVE(opponent, MOVE_THIEF); } } SCENE { MESSAGE("Wobbuffet floats in the air with its Air Balloon!"); MESSAGE("Wobbuffet's Air Balloon popped!"); diff --git a/test/battle/hold_effect/red_card.c b/test/battle/hold_effect/red_card.c index f71ba51330..a21318d246 100644 --- a/test/battle/hold_effect/red_card.c +++ b/test/battle/hold_effect/red_card.c @@ -170,7 +170,7 @@ SINGLE_BATTLE_TEST("Red Card does not activate if stolen by a move") bool32 activate; PARAMETRIZE { item = ITEM_NONE; activate = FALSE; } PARAMETRIZE { item = ITEM_POTION; activate = TRUE; } - ASSUME(MoveHasAdditionalEffect(MOVE_THIEF, MOVE_EFFECT_STEAL_ITEM) == TRUE); + ASSUME(GetMoveEffect(MOVE_THIEF) == EFFECT_STEAL_ITEM); GIVEN { PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } diff --git a/test/battle/hold_effect/restore_stats.c b/test/battle/hold_effect/restore_stats.c index 7f48a05f13..f2508c45ee 100644 --- a/test/battle/hold_effect/restore_stats.c +++ b/test/battle/hold_effect/restore_stats.c @@ -132,23 +132,22 @@ SINGLE_BATTLE_TEST("White Herb wont have time to activate if it is knocked off o PARAMETRIZE { move = MOVE_KNOCK_OFF; } GIVEN { - ASSUME(MoveHasAdditionalEffect(MOVE_THIEF, MOVE_EFFECT_STEAL_ITEM) == TRUE); ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + ASSUME(GetMoveEffect(MOVE_THIEF) == EFFECT_STEAL_ITEM); PLAYER(SPECIES_SLUGMA) { Ability(ABILITY_WEAK_ARMOR); Item(ITEM_WHITE_HERB); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(opponent, move); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, move, opponent); - if (move == MOVE_THIEF) { - MESSAGE("The opposing Wobbuffet stole Slugma's White Herb!"); - } ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Slugma's Weak Armor lowered its Defense!"); MESSAGE("Slugma's Weak Armor raised its Speed!"); if (move == MOVE_KNOCK_OFF) { MESSAGE("The opposing Wobbuffet knocked off Slugma's White Herb!"); + } else if (move == MOVE_THIEF) { + MESSAGE("The opposing Wobbuffet stole Slugma's White Herb!"); } NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); diff --git a/test/battle/move_effect_secondary/spikes.c b/test/battle/move_effect/ceaseless_edge.c similarity index 76% rename from test/battle/move_effect_secondary/spikes.c rename to test/battle/move_effect/ceaseless_edge.c index 1aa2630fd7..5a7e7f4a1a 100644 --- a/test/battle/move_effect_secondary/spikes.c +++ b/test/battle/move_effect/ceaseless_edge.c @@ -3,7 +3,7 @@ ASSUMPTIONS { - ASSUME(MoveHasAdditionalEffect(MOVE_CEASELESS_EDGE, MOVE_EFFECT_SPIKES) == TRUE); + ASSUME(GetMoveEffect(MOVE_CEASELESS_EDGE) == EFFECT_CEASELESS_EDGE); } SINGLE_BATTLE_TEST("Ceaseless Edge sets up hazards after hitting the target") @@ -62,3 +62,19 @@ SINGLE_BATTLE_TEST("Ceaseless Edge can set up to 3 layers of Spikes") MESSAGE("The opposing Wynaut was hurt by the spikes!"); } } + +SINGLE_BATTLE_TEST("Ceaseless Edge fails to set up hazards if user faints") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_ROCKY_HELMET); } + } WHEN { + TURN { MOVE(player, MOVE_CEASELESS_EDGE); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CEASELESS_EDGE, player); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Rocky Helmet!"); + NOT MESSAGE("Spikes were scattered on the ground all around the opposing team!"); + } +} diff --git a/test/battle/move_effect/hit_switch_target.c b/test/battle/move_effect/hit_switch_target.c index e0d6548ec3..82f15bf30f 100644 --- a/test/battle/move_effect/hit_switch_target.c +++ b/test/battle/move_effect/hit_switch_target.c @@ -43,7 +43,7 @@ DOUBLE_BATTLE_TEST("Dragon Tail switches the target with a random non-battler, n } } -SINGLE_BATTLE_TEST("Dragon Tail does not fail if no replacements") +SINGLE_BATTLE_TEST("Dragon Tail fails if no replacements") { GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -56,7 +56,7 @@ SINGLE_BATTLE_TEST("Dragon Tail does not fail if no replacements") } } -SINGLE_BATTLE_TEST("Dragon Tail does not fail if replacements fainted") +SINGLE_BATTLE_TEST("Dragon Tail fails if replacements fainted") { GIVEN { PLAYER(SPECIES_WOBBUFFET); diff --git a/test/battle/move_effect/ice_spinner.c b/test/battle/move_effect/ice_spinner.c index 44ce21eb4f..c04f16b880 100644 --- a/test/battle/move_effect/ice_spinner.c +++ b/test/battle/move_effect/ice_spinner.c @@ -96,7 +96,10 @@ SINGLE_BATTLE_TEST("Ice Spinner doesn't fail if there is no terrain on the field TURN { MOVE(player, MOVE_ICE_SPINNER); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_ICE_SPINNER, player); - NOT MESSAGE("But it failed!"); + NONE_OF { + MESSAGE("But it failed!"); + MESSAGE("Mist swirled around the battlefield!"); + } } } diff --git a/test/battle/move_effect/knock_off.c b/test/battle/move_effect/knock_off.c index 626e0cde28..60be862c4c 100644 --- a/test/battle/move_effect/knock_off.c +++ b/test/battle/move_effect/knock_off.c @@ -99,6 +99,22 @@ SINGLE_BATTLE_TEST("Knock Off does not remove items through Substitute") } } +SINGLE_BATTLE_TEST("Knock Off does not remove items through Substitute even if it breaks it") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(4); HP(4); Item(ITEM_LEFTOVERS); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + MESSAGE("The opposing Wobbuffet's substitute faded!"); + NOT { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); } + } THEN { + EXPECT(opponent->item == ITEM_LEFTOVERS); + } +} + SINGLE_BATTLE_TEST("Knock Off does not remove items through Protect") { GIVEN { @@ -229,18 +245,6 @@ DOUBLE_BATTLE_TEST("Knock Off does not trigger the opposing ally's Symbiosis") } } -SINGLE_BATTLE_TEST("Knock Off doesn't knock off items from Pokemon behind substitutes") -{ - GIVEN { - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_POKE_BALL); } - } WHEN { - TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_KNOCK_OFF); } - } SCENE { - NOT MESSAGE("Wobbuffet knocked off the opposing Wobbuffet's Poké Ball!"); - } -} - SINGLE_BATTLE_TEST("Knock Off does knock off Mega Stones from Pokemon that don't actually use them") { GIVEN { @@ -360,3 +364,33 @@ SINGLE_BATTLE_TEST("Knock Off doesn't knock off begin-battle form-change hold it NOT MESSAGE("Wobbuffet knocked off the opposing Zamazenta's Rusted Shield!"); } } + +SINGLE_BATTLE_TEST("Knock Off does not activate if user faints") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_ROCKY_HELMET); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Rocky Helmet!"); + MESSAGE("Wobbuffet fainted!"); + } THEN { + EXPECT(opponent->item == ITEM_ROCKY_HELMET); + } +} + +SINGLE_BATTLE_TEST("Knock Off doesn't remove item if it's prevented by Sticky Hold") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MUK) { MaxHP(100); HP(51); Item(ITEM_ORAN_BERRY); Ability(ABILITY_STICKY_HOLD); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_STICKY_HOLD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} diff --git a/test/battle/move_effect/stone_axe.c b/test/battle/move_effect/stone_axe.c new file mode 100644 index 0000000000..d65798d18b --- /dev/null +++ b/test/battle/move_effect/stone_axe.c @@ -0,0 +1,94 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_STONE_AXE) == EFFECT_STONE_AXE); +} + +SINGLE_BATTLE_TEST("Stone Axe sets up hazards after hitting the target") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STONE_AXE); } + TURN { SWITCH(opponent, 1); } + } SCENE { + s32 maxHP = GetMonData(&OPPONENT_PARTY[1], MON_DATA_MAX_HP); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, player); + HP_BAR(opponent); + MESSAGE("Pointed stones float in the air around the opposing team!"); + MESSAGE("2 sent out Wobbuffet!"); + HP_BAR(opponent, damage: maxHP / 8); + MESSAGE("Pointed stones dug into the opposing Wobbuffet!"); + } +} + +SINGLE_BATTLE_TEST("Stone Axe can set up pointed stones only once") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_STONE_AXE); } + TURN { MOVE(player, MOVE_STONE_AXE); } + TURN { MOVE(player, MOVE_STONE_AXE); } + TURN { MOVE(player, MOVE_STONE_AXE); } + TURN { SWITCH(opponent, 1); } + } SCENE { + s32 maxHP = GetMonData(&OPPONENT_PARTY[1], MON_DATA_MAX_HP); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, player); + HP_BAR(opponent); + MESSAGE("Pointed stones float in the air around the opposing team!"); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, player); + HP_BAR(opponent); + NOT MESSAGE("Pointed stones float in the air around the opposing team!"); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, player); + HP_BAR(opponent); + NOT MESSAGE("Pointed stones float in the air around the opposing team!"); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, player); + HP_BAR(opponent); + NOT MESSAGE("Pointed stones float in the air around the opposing team!"); + + MESSAGE("2 sent out Wynaut!"); + HP_BAR(opponent, damage: maxHP / 8); + MESSAGE("Pointed stones dug into the opposing Wynaut!"); + } +} + +SINGLE_BATTLE_TEST("Stone Axe sets up hazards after any ability activation") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SKARMORY) { Ability(ABILITY_WEAK_ARMOR); } + } WHEN { + TURN { MOVE(player, MOVE_STONE_AXE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, player); + ABILITY_POPUP(opponent, ABILITY_WEAK_ARMOR); + MESSAGE("Pointed stones float in the air around the opposing team!"); + } +} + +SINGLE_BATTLE_TEST("Stone Axe fails to set up hazards if user faints") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_ROCKY_HELMET); } + } WHEN { + TURN { MOVE(player, MOVE_STONE_AXE); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, player); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Rocky Helmet!"); + NOT MESSAGE("Pointed stones float in the air around the opposing team!"); + } +} diff --git a/test/battle/move_effect_secondary/pay_day.c b/test/battle/move_effect_secondary/pay_day.c index 3b0544a909..fc77e862e2 100644 --- a/test/battle/move_effect_secondary/pay_day.c +++ b/test/battle/move_effect_secondary/pay_day.c @@ -3,7 +3,7 @@ ASSUMPTIONS { - ASSUME(MoveHasAdditionalEffect(MOVE_PAY_DAY, MOVE_EFFECT_PAYDAY)); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_PAY_DAY, MOVE_EFFECT_PAYDAY, 0) == TRUE); } SINGLE_BATTLE_TEST("Pay Day Scatters coins around after it hits - singles") diff --git a/test/battle/move_effect_secondary/steal_item.c b/test/battle/move_effect_secondary/steal_item.c index 8a4ae931d2..7dd922e8b2 100644 --- a/test/battle/move_effect_secondary/steal_item.c +++ b/test/battle/move_effect_secondary/steal_item.c @@ -3,8 +3,8 @@ ASSUMPTIONS { - ASSUME(MoveHasAdditionalEffect(MOVE_THIEF, MOVE_EFFECT_STEAL_ITEM) == TRUE); - ASSUME(MoveHasAdditionalEffect(MOVE_COVET, MOVE_EFFECT_STEAL_ITEM) == TRUE); + ASSUME(GetMoveEffect(MOVE_THIEF == EFFECT_STEAL_ITEM)); + ASSUME(GetMoveEffect(MOVE_COVET == EFFECT_STEAL_ITEM)); } SINGLE_BATTLE_TEST("Thief and Covet steal target's held item") @@ -127,3 +127,41 @@ WILD_BATTLE_TEST("Thief and Covet steal target's held item and it's added to Bag EXPECT_EQ(opponent->item, ITEM_NONE); } } + +SINGLE_BATTLE_TEST("Thief and Covet can't steal target's held item if user faints before") +{ + u32 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); }; + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_ROCKY_HELMET); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_STEAL, opponent); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + EXPECT_EQ(opponent->item, ITEM_ROCKY_HELMET); + } +} + +SINGLE_BATTLE_TEST("Thief and Covet: Berry activation happens before the item can be stolen") +{ + u32 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + GIVEN { + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(200); HP(101); Item(ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_STEAL, opponent); + } +} diff --git a/test/battle/move_effect_secondary/stealth_rock.c b/test/battle/move_effect_secondary/stealth_rock.c index e9101b6591..026db13e7a 100644 --- a/test/battle/move_effect_secondary/stealth_rock.c +++ b/test/battle/move_effect_secondary/stealth_rock.c @@ -3,63 +3,19 @@ ASSUMPTIONS { - ASSUME(MoveHasAdditionalEffect(MOVE_STONE_AXE, MOVE_EFFECT_STEALTH_ROCK) == TRUE); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_STONESURGE, MOVE_EFFECT_STEALTH_ROCK)); } -SINGLE_BATTLE_TEST("Stone Axe sets up hazards after hitting the target") +SINGLE_BATTLE_TEST("Steath Rock: Rock from G-Max Stonesurge are set up before any ability activation") { GIVEN { - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); + PLAYER(SPECIES_DREDNAW) { GigantamaxFactor(TRUE); } + OPPONENT(SPECIES_SKARMORY) { Ability(ABILITY_WEAK_ARMOR); } } WHEN { - TURN { MOVE(player, MOVE_STONE_AXE); } - TURN { SWITCH(opponent, 1); } + TURN { MOVE(player, MOVE_WATERFALL, gimmick: GIMMICK_DYNAMAX); } } SCENE { - s32 maxHP = GetMonData(&OPPONENT_PARTY[1], MON_DATA_MAX_HP); - ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, player); - HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_G_MAX_STONESURGE, player); MESSAGE("Pointed stones float in the air around the opposing team!"); - MESSAGE("2 sent out Wobbuffet!"); - HP_BAR(opponent, damage: maxHP / 8); - MESSAGE("Pointed stones dug into the opposing Wobbuffet!"); + ABILITY_POPUP(opponent, ABILITY_WEAK_ARMOR); } } - -SINGLE_BATTLE_TEST("Stone Axe can set up pointed stones only once") -{ - GIVEN { - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WYNAUT); - } WHEN { - TURN { MOVE(player, MOVE_STONE_AXE); } - TURN { MOVE(player, MOVE_STONE_AXE); } - TURN { MOVE(player, MOVE_STONE_AXE); } - TURN { MOVE(player, MOVE_STONE_AXE); } - TURN { SWITCH(opponent, 1); } - } SCENE { - s32 maxHP = GetMonData(&OPPONENT_PARTY[1], MON_DATA_MAX_HP); - - ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, player); - HP_BAR(opponent); - MESSAGE("Pointed stones float in the air around the opposing team!"); - - ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, player); - HP_BAR(opponent); - NOT MESSAGE("Pointed stones float in the air around the opposing team!"); - - ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, player); - HP_BAR(opponent); - NOT MESSAGE("Pointed stones float in the air around the opposing team!"); - - ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, player); - HP_BAR(opponent); - NOT MESSAGE("Pointed stones float in the air around the opposing team!"); - - MESSAGE("2 sent out Wynaut!"); - HP_BAR(opponent, damage: maxHP / 8); - MESSAGE("Pointed stones dug into the opposing Wynaut!"); - } -} - diff --git a/test/battle/move_effects_combined/make_it_rain.c b/test/battle/move_effects_combined/make_it_rain.c index e0e00015a4..655a34f8be 100644 --- a/test/battle/move_effects_combined/make_it_rain.c +++ b/test/battle/move_effects_combined/make_it_rain.c @@ -3,7 +3,7 @@ ASSUMPTIONS { - ASSUME(MoveHasAdditionalEffect(MOVE_MAKE_IT_RAIN, MOVE_EFFECT_PAYDAY)); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_MAKE_IT_RAIN, MOVE_EFFECT_PAYDAY, 0) == TRUE); ASSUME(MoveHasAdditionalEffectSelf(MOVE_MAKE_IT_RAIN, MOVE_EFFECT_SP_ATK_MINUS_1)); } From 674d46bb0f18462e19eaaf72782d4ab25f9d7c7f Mon Sep 17 00:00:00 2001 From: AlexOn1ine Date: Tue, 15 Jul 2025 15:37:23 +0200 Subject: [PATCH 09/17] Fixes incorrect recoil damage after a berry was eaten --- src/battle_script_commands.c | 2 +- test/battle/move_flags/recoil.c | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index cb746ebc99..8abed761e7 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6380,7 +6380,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) case EFFECT_RECOIL: if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) { - gBattleStruct->moveDamage[gBattlerAttacker] = max(1, gBattleStruct->moveDamage[gBattlerTarget] * max(1, GetMoveRecoil(gCurrentMove)) / 100); + gBattleStruct->moveDamage[gBattlerAttacker] = max(1, gBattleScripting.savedDmg * max(1, GetMoveRecoil(gCurrentMove)) / 100); BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_MoveEffectRecoil; effect = TRUE; diff --git a/test/battle/move_flags/recoil.c b/test/battle/move_flags/recoil.c index 42b8b1a76b..c2b2e21b86 100644 --- a/test/battle/move_flags/recoil.c +++ b/test/battle/move_flags/recoil.c @@ -102,3 +102,23 @@ SINGLE_BATTLE_TEST("Recoil: Flare Blitz is absorbed by Flash Fire and no recoil } } } + +SINGLE_BATTLE_TEST("Recoil: The correct amount of recoil damage is dealt after targets recovery berry proc") +{ + s16 directDamage; + s16 recoilDamage; + + GIVEN { + ASSUME(GetMoveRecoil(MOVE_TAKE_DOWN) == 25); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); }; + } WHEN { + TURN { MOVE(player, MOVE_TAKE_DOWN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAKE_DOWN, player); + HP_BAR(opponent, captureDamage: &directDamage); + HP_BAR(player, captureDamage: &recoilDamage); + } THEN { + EXPECT_MUL_EQ(directDamage, UQ_4_12(0.25), recoilDamage); + } +} From 0e37bfd7ff1ecd97bbaa29ae0174051487a4bee9 Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Tue, 15 Jul 2025 12:07:26 -0400 Subject: [PATCH 10/17] Fix stat change / stat ID ambiguity (#7346) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- src/battle_ai_util.c | 42 +++++++++++++++++++++++++++++++++++------- test/battle/ai/ai.c | 13 +++++++++++++ 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 2d7136eefc..7b5e8e0d37 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -4141,13 +4141,41 @@ bool32 IsRecycleEncouragedItem(u32 item) return FALSE; } -static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef, enum StatChange statId, bool32 considerContrary) +static u32 GetStatBeingChanged(enum StatChange statChange) +{ + switch(statChange) + { + case STAT_CHANGE_ATK: + case STAT_CHANGE_ATK_2: + return STAT_ATK; + case STAT_CHANGE_DEF: + case STAT_CHANGE_DEF_2: + return STAT_DEF; + case STAT_CHANGE_SPEED: + case STAT_CHANGE_SPEED_2: + return STAT_SPEED; + case STAT_CHANGE_SPATK: + case STAT_CHANGE_SPATK_2: + return STAT_SPATK; + case STAT_CHANGE_SPDEF: + case STAT_CHANGE_SPDEF_2: + return STAT_SPDEF; + case STAT_CHANGE_ACC: + return STAT_ACC; + case STAT_CHANGE_EVASION: + return STAT_EVASION; + } + return 0; // STAT_HP, should never be getting changed +} + +static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef, enum StatChange statChange, bool32 considerContrary) { enum AIScore tempScore = NO_INCREASE; u32 noOfHitsToFaint = NoOfHitsForTargetToFaintAI(battlerDef, battlerAtk); u32 aiIsFaster = AI_IsFaster(battlerAtk, battlerDef, TRUE); u32 shouldSetUp = ((noOfHitsToFaint >= 2 && aiIsFaster) || (noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == UNKNOWN_NO_OF_HITS); u32 i; + u32 statId = GetStatBeingChanged(statChange); if (considerContrary && gAiLogicData->abilities[battlerAtk] == ABILITY_CONTRARY) return NO_INCREASE; @@ -4206,7 +4234,7 @@ static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef, tempScore += WEAK_EFFECT; } - switch (statId) + switch (statChange) { case STAT_CHANGE_ATK: if (HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL) && shouldSetUp) @@ -4269,7 +4297,7 @@ static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef, } break; case STAT_CHANGE_ACC: - if (gBattleMons[battlerAtk].statStages[STAT_ACC] <= 3) // Increase only if necessary + if (gBattleMons[battlerAtk].statStages[statId] <= 3) // Increase only if necessary tempScore += DECENT_EFFECT; break; case STAT_CHANGE_EVASION: @@ -4283,14 +4311,14 @@ static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef, return tempScore; } -u32 IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, enum StatChange statId) +u32 IncreaseStatUpScore(u32 battlerAtk, u32 battlerDef, enum StatChange statChange) { - return IncreaseStatUpScoreInternal(battlerAtk, battlerDef, statId, TRUE); + return IncreaseStatUpScoreInternal(battlerAtk, battlerDef, statChange, TRUE); } -u32 IncreaseStatUpScoreContrary(u32 battlerAtk, u32 battlerDef, enum StatChange statId) +u32 IncreaseStatUpScoreContrary(u32 battlerAtk, u32 battlerDef, enum StatChange statChange) { - return IncreaseStatUpScoreInternal(battlerAtk, battlerDef, statId, FALSE); + return IncreaseStatUpScoreInternal(battlerAtk, battlerDef, statChange, FALSE); } void IncreasePoisonScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index 3fe975fc66..62eae623bc 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -881,3 +881,16 @@ AI_SINGLE_BATTLE_TEST("Move scoring comparison properly awards bonus point to be TURN { EXPECT_MOVE(opponent, MOVE_WATER_SPOUT); } } } + +AI_SINGLE_BATTLE_TEST("AI will stop setting up at +4") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE, MOVE_IRON_DEFENSE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_IRON_DEFENSE); } + TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_IRON_DEFENSE); } + TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_TACKLE); } + } +} From da3d223c45c73e5f7216eb75f1a441258be6ad64 Mon Sep 17 00:00:00 2001 From: AERDU Date: Wed, 16 Jul 2025 10:41:32 +0000 Subject: [PATCH 11/17] fallback bugfix for B_DARK_VOID_FAIL < GEN_7 (#7351) --- data/battle_scripts_1.s | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index a4d3264d13..d694e47467 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -2856,12 +2856,6 @@ BattleScript_MoveMissed:: waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd -BattleScript_EffectDarkVoid:: -.if B_DARK_VOID_FAIL >= GEN_7 - jumpifspecies BS_ATTACKER, SPECIES_DARKRAI, BattleScript_EffectNonVolatileStatus - goto BattleScript_PokemonCantUseTheMove -.endif - BattleScript_TerrainPreventsEnd2:: pause B_WAIT_TIME_SHORT printfromtable gTerrainPreventsStringIds @@ -4683,6 +4677,11 @@ BattleScript_FlatterTryConfuse:: seteffectprimary MOVE_EFFECT_CONFUSION goto BattleScript_MoveEnd +BattleScript_EffectDarkVoid:: +.if B_DARK_VOID_FAIL >= GEN_7 + jumpifspecies BS_ATTACKER, SPECIES_DARKRAI, BattleScript_EffectNonVolatileStatus + goto BattleScript_PokemonCantUseTheMove +.endif BattleScript_EffectNonVolatileStatus:: attackcanceler attackstring From 67d007f4fb811b2e72a773fb282213b4dddd2741 Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Wed, 16 Jul 2025 21:29:25 +0100 Subject: [PATCH 12/17] Fixes Grassy Terrain healing when Heal Blocked/Semi-Invulnerable (#7353) --- src/battle_end_turn.c | 8 +++++++- src/battle_message.c | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/battle_end_turn.c b/src/battle_end_turn.c index 6cf45ded4d..efc790aadd 100644 --- a/src/battle_end_turn.c +++ b/src/battle_end_turn.c @@ -490,10 +490,16 @@ static bool32 HandleEndTurnFirstEventBlock(u32 battler) gBattleStruct->eventBlockCounter++; break; case FIRST_EVENT_BLOCK_GRASSY_TERRAIN_HEAL: - if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && IsBattlerAlive(battler) && !IsBattlerAtMaxHp(battler) && IsBattlerGrounded(battler)) + if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN + && IsBattlerAlive(battler) + && !IsBattlerAtMaxHp(battler) + && !(gStatuses3[battler] & (STATUS3_SEMI_INVULNERABLE | STATUS3_HEAL_BLOCK)) + && IsBattlerGrounded(battler)) { gBattlerAttacker = battler; gBattleStruct->moveDamage[battler] = -(GetNonDynamaxMaxHP(battler) / 16); + if (gBattleStruct->moveDamage[battler] == 0) + gBattleStruct->moveDamage[battler] = -1; BattleScriptExecute(BattleScript_GrassyTerrainHeals); effect = TRUE; } diff --git a/src/battle_message.c b/src/battle_message.c index 350cddd374..ab1ec0659f 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -1205,7 +1205,8 @@ const u16 gGotBurnedStringIds[] = const u16 gGotFrostbiteStringIds[] = { - [B_MSG_STATUSED] = STRINGID_PKMNGOTFROSTBITE + [B_MSG_STATUSED] = STRINGID_PKMNGOTFROSTBITE, + [B_MSG_STATUSED_BY_ABILITY] = STRINGID_PKMNGOTFROSTBITE, }; const u16 gFrostbiteHealedStringIds[] = From 19c9aa508d91338ab298a7363c066895e060a5dc Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Wed, 16 Jul 2025 21:31:06 +0100 Subject: [PATCH 13/17] Fixes shadows and mon animations for Illusioned battlers (#7347) --- src/battle_controllers.c | 10 ++++------ src/battle_gfx_sfx_util.c | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/battle_controllers.c b/src/battle_controllers.c index d189c67215..dfd7138bcb 100644 --- a/src/battle_controllers.c +++ b/src/battle_controllers.c @@ -2172,9 +2172,7 @@ void StartSendOutAnim(u32 battler, bool32 dontClearTransform, bool32 dontClearSu ClearTemporarySpeciesSpriteData(battler, dontClearTransform, dontClearSubstituteBit); gBattlerPartyIndexes[battler] = gBattleResources->bufferA[battler][1]; - species = GetIllusionMonSpecies(battler); - if (species == SPECIES_NONE) - species = GetMonData(mon, MON_DATA_SPECIES); + species = GetBattlerVisualSpecies(battler); gBattleControllerData[battler] = CreateInvisibleSpriteWithCallback(SpriteCB_WaitForBattlerBallReleaseAnim); // Load sprite for opponent only, player sprite is expected to be already loaded. if (!IsOnPlayerSide(battler)) @@ -2490,7 +2488,7 @@ void BtlController_HandleSetRawMonData(u32 battler) void BtlController_HandleLoadMonSprite(u32 battler, void (*controllerCallback)(u32 battler)) { struct Pokemon *mon = GetBattlerMon(battler); - u16 species = GetMonData(mon, MON_DATA_SPECIES); + u16 species = GetBattlerVisualSpecies(battler); BattleLoadMonSpriteGfx(mon, battler); SetMultiuseSpriteTemplateToPokemon(species, GetBattlerPosition(battler)); @@ -3153,7 +3151,7 @@ void TrySetBattlerShadowSpriteCallback(u32 battler) if (gSprites[gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdPrimary].callback == SpriteCallbackDummy && (B_ENEMY_MON_SHADOW_STYLE <= GEN_3 || P_GBA_STYLE_SPECIES_GFX == TRUE || gSprites[gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdSecondary].callback == SpriteCallbackDummy)) - SetBattlerShadowSpriteCallback(battler, GetMonData(GetBattlerMon(battler), MON_DATA_SPECIES)); + SetBattlerShadowSpriteCallback(battler, GetBattlerVisualSpecies(battler)); } bool32 TryShinyAnimAfterMonAnimUtil(u32 battler) @@ -3256,7 +3254,7 @@ bool32 SwitchIn_TryShinyAnimUtil(u32 battler) DestroySprite(&gSprites[gBattleControllerData[battler]]); if (GetBattlerSide(battler) == B_SIDE_OPPONENT) - SetBattlerShadowSpriteCallback(battler, GetMonData(GetBattlerMon(battler), MON_DATA_SPECIES)); + SetBattlerShadowSpriteCallback(battler, GetBattlerVisualSpecies(battler)); return TRUE; } diff --git a/src/battle_gfx_sfx_util.c b/src/battle_gfx_sfx_util.c index 3d8d2f48be..9b272a51b4 100644 --- a/src/battle_gfx_sfx_util.c +++ b/src/battle_gfx_sfx_util.c @@ -1168,7 +1168,7 @@ void CreateEnemyShadowSprite(u32 battler) { if (B_ENEMY_MON_SHADOW_STYLE >= GEN_4 && P_GBA_STYLE_SPECIES_GFX == FALSE) { - u16 species = SanitizeSpeciesId(gBattleMons[battler].species); + u16 species = GetBattlerVisualSpecies(battler); u8 size = gSpeciesInfo[species].enemyShadowSize; gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdPrimary = CreateSprite(&gSpriteTemplate_EnemyShadow, @@ -1282,7 +1282,7 @@ void SpriteCB_EnemyShadow(struct Sprite *shadowSprite) } else if (B_ENEMY_MON_SHADOW_STYLE >= GEN_4 && P_GBA_STYLE_SPECIES_GFX == FALSE) { - u16 species = SanitizeSpeciesId(gBattleMons[battler].species); + u16 species = GetBattlerVisualSpecies(battler); xOffset = gSpeciesInfo[species].enemyShadowXOffset + (shadowSprite->tSpriteSide == SPRITE_SIDE_LEFT ? -16 : 16); yOffset = gSpeciesInfo[species].enemyShadowYOffset + 16; size = gSpeciesInfo[species].enemyShadowSize; From 705f194d985fec9b228f1a09bd631f2d2fff98de Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 18 Jul 2025 12:28:00 +0200 Subject: [PATCH 14/17] Fixes various battler corruption during switch in ability activation (#7352) --- charmap.txt | 4 +- data/battle_scripts_1.s | 82 ++++++++++++------------------------ include/battle_message.h | 4 +- include/battle_scripts.h | 8 ++-- src/battle_message.c | 16 +++---- src/battle_script_commands.c | 7 ++- src/battle_util.c | 37 ++++++++-------- 7 files changed, 70 insertions(+), 88 deletions(-) diff --git a/charmap.txt b/charmap.txt index 34fb683d0c..2750610dc9 100644 --- a/charmap.txt +++ b/charmap.txt @@ -424,8 +424,8 @@ B_TRAINER1_NAME_WITH_CLASS = FD 42 B_TRAINER2_NAME_WITH_CLASS = FD 43 B_PARTNER_NAME_WITH_CLASS = FD 44 B_ATK_TRAINER_NAME_WITH_CLASS = FD 45 -B_SCR_TEAM1 = FD 46 -B_SCR_TEAM2 = FD 47 +B_EFF_TEAM1 = FD 46 +B_EFF_TEAM2 = FD 47 @ indicates the end of a town/city name (before " TOWN" or " CITY") NAME_END = FC 00 diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index d694e47467..6d36b2b530 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -6763,51 +6763,40 @@ BattleScript_UltraBurst:: end3 BattleScript_GulpMissileFormChange:: - call BattleScript_AttackerFormChange + call BattleScript_BattlerFormChange goto BattleScript_FromTwoTurnMovesSecondTurnRet -BattleScript_AttackerFormChange:: +BattleScript_BattlerFormChange:: pause 5 - copybyte gBattlerAbility, gBattlerAttacker - call BattleScript_AbilityPopUp + call BattleScript_AbilityPopUpScripting flushtextbox -BattleScript_AttackerFormChangeNoPopup:: - handleformchange BS_ATTACKER, 0 - handleformchange BS_ATTACKER, 1 - playanimation BS_ATTACKER, B_ANIM_FORM_CHANGE +BattleScript_BattlerFormChangeNoPopup: + handleformchange BS_SCRIPTING, 0 + handleformchange BS_SCRIPTING, 1 + playanimation BS_SCRIPTING, B_ANIM_FORM_CHANGE waitanimation - handleformchange BS_ATTACKER, 2 + handleformchange BS_SCRIPTING, 2 return -BattleScript_AttackerFormChangeEnd3:: - call BattleScript_AttackerFormChange +BattleScript_BattlerFormChangeEnd3:: + call BattleScript_BattlerFormChange end3 -BattleScript_AttackerFormChangeEnd3NoPopup:: - call BattleScript_AttackerFormChangeNoPopup +BattleScript_BattlerFormChangeEnd3NoPopup:: + call BattleScript_BattlerFormChangeNoPopup end3 -BattleScript_AttackerFormChangeWithString:: +BattleScript_BattlerFormChangeWithStringEnd3:: pause 5 - copybyte gBattlerAbility, gBattlerAttacker - call BattleScript_AbilityPopUp + call BattleScript_AbilityPopUpScripting flushtextbox -BattleScript_AttackerFormChangeWithStringNoPopup:: - handleformchange BS_ATTACKER, 0 - handleformchange BS_ATTACKER, 1 - playanimation BS_ATTACKER, B_ANIM_FORM_CHANGE + handleformchange BS_SCRIPTING, 0 + handleformchange BS_SCRIPTING, 1 + playanimation BS_SCRIPTING, B_ANIM_FORM_CHANGE waitanimation - handleformchange BS_ATTACKER, 2 + handleformchange BS_SCRIPTING, 2 printstring STRINGID_PKMNTRANSFORMED waitmessage B_WAIT_TIME_LONG - return - -BattleScript_AttackerFormChangeWithStringEnd3:: - call BattleScript_AttackerFormChangeWithString - end3 - -BattleScript_AttackerFormChangeWithStringEnd3NoPopup:: - call BattleScript_AttackerFormChangeWithStringNoPopup end3 BattleScript_AttackerFormChangeMoveEffect:: @@ -6873,19 +6862,6 @@ BattleScript_TargetFormChangeWithStringNoPopup:: waitmessage B_WAIT_TIME_LONG return -BattleScript_BattlerFormChangeWithStringEnd3:: - pause 5 - call BattleScript_AbilityPopUpScripting - flushtextbox - handleformchange BS_SCRIPTING, 0 - handleformchange BS_SCRIPTING, 1 - playanimation BS_SCRIPTING, B_ANIM_FORM_CHANGE, NULL - waitanimation - handleformchange BS_SCRIPTING, 2 - printstring STRINGID_PKMNTRANSFORMED - waitmessage B_WAIT_TIME_LONG - end3 - BattleScript_IllusionOffAndTerastallization:: call BattleScript_IllusionOff goto BattleScript_Terastallization @@ -7620,11 +7596,7 @@ BattleScript_TryIntimidateHoldEffectsRet: BattleScript_IntimidateActivates:: savetarget -.if B_ABILITY_POP_UP == TRUE - showabilitypopup BS_ATTACKER - pause B_WAIT_TIME_LONG - destroyabilitypopup -.endif + call BattleScript_AbilityPopUp setbyte gBattlerTarget, 0 BattleScript_IntimidateLoop: jumpifbyteequal gBattlerTarget, gBattlerAttacker, BattleScript_IntimidateLoopIncrement @@ -7655,6 +7627,7 @@ BattleScript_IntimidateLoopIncrement: copybyte sBATTLER, gBattlerAttacker destroyabilitypopup restoretarget + restoreattacker pause B_WAIT_TIME_MED tryintimidateejectpack end3 @@ -7689,11 +7662,7 @@ BattleScript_IntimidateInReverse:: BattleScript_SupersweetSyrupActivates:: savetarget -.if B_ABILITY_POP_UP == TRUE - showabilitypopup BS_ATTACKER - pause B_WAIT_TIME_LONG - destroyabilitypopup -.endif + call BattleScript_AbilityPopUp printstring STRINGID_SUPERSWEETAROMAWAFTS waitmessage B_WAIT_TIME_LONG setbyte gBattlerTarget, 0 @@ -7721,6 +7690,7 @@ BattleScript_SupersweetSyrupLoopIncrement: copybyte sBATTLER, gBattlerAttacker destroyabilitypopup restoretarget + restoreattacker pause B_WAIT_TIME_MED tryintimidateejectpack end3 @@ -7877,8 +7847,8 @@ BattleScript_HospitalityActivates:: printstring STRINGID_HOSPITALITYRESTORATION waitmessage B_WAIT_TIME_LONG orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE - healthbarupdate BS_TARGET - datahpupdate BS_TARGET + healthbarupdate BS_EFFECT_BATTLER + datahpupdate BS_EFFECT_BATTLER end3 BattleScript_AttackWeakenedByStrongWinds:: @@ -8345,6 +8315,7 @@ BattleScript_FellStingerRaisesAtkEnd: BattleScript_AttackerAbilityStatRaiseEnd3:: call BattleScript_AttackerAbilityStatRaise + restoreattacker end3 BattleScript_SwitchInAbilityMsg:: @@ -8395,6 +8366,8 @@ BattleScript_ImposterActivates:: waitanimation printstring STRINGID_IMPOSTERTRANSFORM waitmessage B_WAIT_TIME_LONG + restoreattacker + restoretarget end3 BattleScript_HurtAttacker: @@ -9495,6 +9468,7 @@ BattleScript_PastelVeilLoopIncrement: setallytonexttarget BattleScript_PastelVeil_TryCurePoison goto BattleScript_PastelVeilEnd BattleScript_PastelVeilEnd: + restoretarget end3 BattleScript_NeutralizingGasExits:: diff --git a/include/battle_message.h b/include/battle_message.h index e9b133b656..28be583551 100644 --- a/include/battle_message.h +++ b/include/battle_message.h @@ -84,8 +84,8 @@ #define B_TXT_TRAINER2_NAME_WITH_CLASS 0x43 #define B_TXT_PARTNER_NAME_WITH_CLASS 0x44 #define B_TXT_ATK_TRAINER_NAME_WITH_CLASS 0x45 -#define B_TXT_SCR_TEAM1 0x46 -#define B_TXT_SCR_TEAM2 0x47 +#define B_TXT_EFF_TEAM1 0x46 +#define B_TXT_EFF_TEAM2 0x47 #define B_BUFF_STRING 0 #define B_BUFF_NUMBER 1 diff --git a/include/battle_scripts.h b/include/battle_scripts.h index cd88a120a5..372921e8e8 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -374,10 +374,10 @@ extern const u8 BattleScript_IllusionOffEnd3[]; extern const u8 BattleScript_IllusionOffAndTerastallization[]; extern const u8 BattleScript_DancerActivates[]; extern const u8 BattleScript_AftermathDmg[]; -extern const u8 BattleScript_AttackerFormChange[]; -extern const u8 BattleScript_AttackerFormChangeEnd3[]; +extern const u8 BattleScript_BattlerFormChange[]; +extern const u8 BattleScript_BattlerFormChangeEnd3[]; extern const u8 BattleScript_AttackerFormChangeWithString[]; -extern const u8 BattleScript_AttackerFormChangeWithStringEnd3[]; +extern const u8 BattleScript_BattlerFormChangeWithStringEnd3[]; extern const u8 BattleScript_TargetFormChange[]; extern const u8 BattleScript_AnticipationActivates[]; extern const u8 BattleScript_SlowStartEnds[]; @@ -449,7 +449,7 @@ extern const u8 BattleScript_WanderingSpiritActivates[]; extern const u8 BattleScript_MirrorArmorReflect[]; extern const u8 BattleScript_GooeyActivates[]; extern const u8 BattleScript_PastelVeilActivates[]; -extern const u8 BattleScript_AttackerFormChangeEnd3NoPopup[]; +extern const u8 BattleScript_BattlerFormChangeEnd3NoPopup[]; extern const u8 BattleScript_AttackerFormChangeWithStringEnd3NoPopup[]; extern const u8 BattleScript_AttackerFormChangeMoveEffect[]; extern const u8 BattleScript_BothCanNoLongerEscape[]; diff --git a/src/battle_message.c b/src/battle_message.c index ab1ec0659f..3c1ca4b14b 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -319,7 +319,7 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] = [STRINGID_PKMNENDUREDHIT] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} endured the hit!"), [STRINGID_MAGNITUDESTRENGTH] = COMPOUND_STRING("Magnitude {B_BUFF1}!"), [STRINGID_PKMNCUTHPMAXEDATTACK] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} cut its own HP and maximized its Attack!"), - [STRINGID_PKMNCOPIEDSTATCHANGES] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} copied {B_DEF_NAME_WITH_PREFIX2}'s stat changes!"), + [STRINGID_PKMNCOPIEDSTATCHANGES] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX} copied {B_EFF_NAME_WITH_PREFIX2}'s stat changes!"), [STRINGID_PKMNGOTFREE] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} got free of {B_DEF_NAME_WITH_PREFIX2}'s {B_BUFF1}!"), //not in gen 5+, generic rapid spin? [STRINGID_PKMNSHEDLEECHSEED] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} shed Leech Seed!"), //not in gen 5+, generic rapid spin? [STRINGID_PKMNBLEWAWAYSPIKES] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} blew away Spikes!"), //not in gen 5+, generic rapid spin? @@ -629,13 +629,13 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] = [STRINGID_ICEBODYHPGAIN] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s {B_ATK_ABILITY} healed it a little bit!"), //don't think this message is displayed anymore [STRINGID_SNOWWARNINGHAIL] = COMPOUND_STRING("It started to hail!"), [STRINGID_FRISKACTIVATES] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} frisked {B_DEF_NAME_WITH_PREFIX2} and found its {B_LAST_ITEM}!"), - [STRINGID_UNNERVEENTERS] = COMPOUND_STRING("{B_DEF_TEAM1} team is too nervous to eat Berries!"), + [STRINGID_UNNERVEENTERS] = COMPOUND_STRING("{B_EFF_TEAM1} team is too nervous to eat Berries!"), [STRINGID_HARVESTBERRY] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} harvested its {B_LAST_ITEM}!"), [STRINGID_MAGICBOUNCEACTIVATES] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} bounced the {B_ATK_NAME_WITH_PREFIX2} back!"), [STRINGID_PROTEANTYPECHANGE] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s {B_ATK_ABILITY} transformed it into the {B_BUFF1} type!"), [STRINGID_SYMBIOSISITEMPASS] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX} passed its {B_LAST_ITEM} to {B_EFF_NAME_WITH_PREFIX2} through {B_LAST_ABILITY}!"), [STRINGID_STEALTHROCKDMG] = COMPOUND_STRING("Pointed stones dug into {B_SCR_NAME_WITH_PREFIX2}!"), - [STRINGID_TOXICSPIKESABSORBED] = COMPOUND_STRING("The poison spikes disappeared from the ground around {B_SCR_TEAM2} team!"), + [STRINGID_TOXICSPIKESABSORBED] = COMPOUND_STRING("The poison spikes disappeared from the ground around {B_EFF_TEAM2} team!"), [STRINGID_TOXICSPIKESPOISONED] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX} was poisoned!"), [STRINGID_TOXICSPIKESBADLYPOISONED] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX} was badly poisoned!"), [STRINGID_STICKYWEBSWITCHIN] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX} was caught in a sticky web!"), @@ -857,7 +857,7 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] = [STRINGID_SWAMPENVELOPEDSIDE] = COMPOUND_STRING("A swamp enveloped {B_DEF_TEAM2} team!"), [STRINGID_THESWAMPDISAPPEARED] = COMPOUND_STRING("The swamp around {B_ATK_TEAM2} team disappeared!"), [STRINGID_PKMNTELLCHILLINGRECEPTIONJOKE] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} is preparing to tell a chillingly bad joke!"), - [STRINGID_HOSPITALITYRESTORATION] = COMPOUND_STRING("{B_ATK_PARTNER_NAME} drank down all the matcha that {B_ATK_NAME_WITH_PREFIX2} made!"), + [STRINGID_HOSPITALITYRESTORATION] = COMPOUND_STRING("{B_EFF_NAME_WITH_PREFIX} drank down all the matcha that {B_SCR_NAME_WITH_PREFIX2} made!"), [STRINGID_ELECTROSHOTCHARGING] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} absorbed electricity!"), [STRINGID_ITEMWASUSEDUP] = COMPOUND_STRING("The {B_LAST_ITEM} was used up…"), [STRINGID_ATTACKERLOSTITSTYPE] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} lost its {B_BUFF1} type!"), @@ -3111,14 +3111,14 @@ u32 BattleStringExpandPlaceholders(const u8 *src, u8 *dst, u32 dstSize) else toCpy = sText_Opposing2; break; - case B_TXT_SCR_TEAM1: - if (IsOnPlayerSide(gBattleScripting.battler)) + case B_TXT_EFF_TEAM1: + if (IsOnPlayerSide(gEffectBattler)) toCpy = sText_Your1; else toCpy = sText_Opposing1; break; - case B_TXT_SCR_TEAM2: - if (IsOnPlayerSide(gBattleScripting.battler)) + case B_TXT_EFF_TEAM2: + if (IsOnPlayerSide(gEffectBattler)) toCpy = sText_Your2; else toCpy = sText_Opposing2; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 8abed761e7..fcd325305c 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -8325,7 +8325,7 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) { gSideStatuses[GetBattlerSide(battler)] &= ~SIDE_STATUS_TOXIC_SPIKES; gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount = 0; - gBattleScripting.battler = battler; + gEffectBattler = battler; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_ToxicSpikesAbsorbed; } @@ -17877,9 +17877,14 @@ void BS_TryGulpMissile(void) && (gCurrentMove == MOVE_DIVE) && (GetBattlerAbility(gBattlerAttacker) == ABILITY_GULP_MISSILE) && TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_HP_PERCENT)) + { + gBattleScripting.battler = gBattlerAttacker; gBattlescriptCurrInstr = BattleScript_GulpMissileFormChange; + } else + { gBattlescriptCurrInstr = cmd->nextInstr; + } } void BS_TryActivateGulpMissile(void) diff --git a/src/battle_util.c b/src/battle_util.c index 3e9090516e..f3972791f8 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -1821,7 +1821,8 @@ static inline bool32 TryFormChangeBeforeMove(void) return FALSE; BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_AttackerFormChange; + gBattleScripting.battler = gBattlerAttacker; + gBattlescriptCurrInstr = BattleScript_BattlerFormChange; return TRUE; } @@ -3515,6 +3516,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && gBattleStruct->illusion[diagonalBattler].state != ILLUSION_ON && !(gStatuses3[diagonalBattler] & STATUS3_SEMI_INVULNERABLE_NO_COMMANDER)) { + SaveBattlerAttacker(gBattlerAttacker); + SaveBattlerTarget(gBattlerTarget); gBattlerAttacker = battler; gBattlerTarget = diagonalBattler; BattleScriptPushCursorAndCallback(BattleScript_ImposterActivates); @@ -3562,7 +3565,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_UNNERVE: if (!gSpecialStatuses[battler].switchInAbilityDone && !gDisableStructs[battler].unnerveActivated) { - gBattlerTarget = GetOppositeBattler(battler); + gEffectBattler = GetOppositeBattler(battler); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_UNNERVE; gDisableStructs[battler].unnerveActivated = TRUE; gSpecialStatuses[battler].switchInAbilityDone = TRUE; @@ -3574,7 +3577,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_AS_ONE_SHADOW_RIDER: if (!gSpecialStatuses[battler].switchInAbilityDone && !gDisableStructs[battler].unnerveActivated) { - gBattlerTarget = GetOppositeBattler(battler); + gEffectBattler = GetOppositeBattler(battler); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_ASONE; gDisableStructs[battler].unnerveActivated = TRUE; gSpecialStatuses[battler].switchInAbilityDone = TRUE; @@ -3596,6 +3599,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_PASTEL_VEIL: if (!gSpecialStatuses[battler].switchInAbilityDone) { + SaveBattlerTarget(gBattlerTarget); gBattlerTarget = battler; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_PASTEL_VEIL; BattleScriptPushCursorAndCallback(BattleScript_PastelVeilActivates); @@ -3635,7 +3639,6 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (!gSpecialStatuses[battler].switchInAbilityDone) { gSpecialStatuses[battler].switchInAbilityDone = TRUE; - gBattleScripting.battler = battler; BattleScriptPushCursorAndCallback(BattleScript_FriskActivates); // Try activate effect++; } @@ -3680,6 +3683,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN)) { SET_STATCHANGER(statId, 1, FALSE); + SaveBattlerAttacker(gBattlerAttacker); gBattlerAttacker = battler; PREPARE_STAT_BUFFER(gBattleTextBuff1, statId); BattleScriptPushCursorAndCallback(BattleScript_AttackerAbilityStatRaiseEnd3); @@ -3830,6 +3834,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_INTIMIDATE: if (!gSpecialStatuses[battler].switchInAbilityDone) { + SaveBattlerAttacker(gBattlerAttacker); gBattlerAttacker = battler; gSpecialStatuses[battler].switchInAbilityDone = TRUE; SET_STATCHANGER(STAT_ATK, 1, TRUE); @@ -3841,6 +3846,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (!gSpecialStatuses[battler].switchInAbilityDone && !gBattleStruct->partyState[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]].supersweetSyrup) { + SaveBattlerAttacker(gBattlerAttacker); gBattlerAttacker = battler; gSpecialStatuses[battler].switchInAbilityDone = TRUE; gBattleStruct->partyState[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]].supersweetSyrup = TRUE; @@ -3873,8 +3879,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_SHIELDS_DOWN: if (TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT)) { - gBattlerAttacker = battler; - BattleScriptPushCursorAndCallback(BattleScript_AttackerFormChangeEnd3); + BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeEnd3); effect++; } break; @@ -4002,10 +4007,9 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && CountBattlerStatIncreases(BATTLE_PARTNER(battler), FALSE)) { gSpecialStatuses[battler].switchInAbilityDone = TRUE; - gBattlerAttacker = gBattlerAbility = battler; for (i = 0; i < NUM_BATTLE_STATS; i++) gBattleMons[battler].statStages[i] = gBattleMons[BATTLE_PARTNER(battler)].statStages[i]; - gBattleScripting.battler = BATTLE_PARTNER(battler); + gEffectBattler = BATTLE_PARTNER(battler); BattleScriptPushCursorAndCallback(BattleScript_CostarActivates); effect++; } @@ -4033,8 +4037,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && gBattleMons[partner].hp < gBattleMons[partner].maxHP && IsBattlerAlive(partner)) { - gBattlerTarget = partner; - gBattlerAttacker = battler; + gEffectBattler = partner; gSpecialStatuses[battler].switchInAbilityDone = TRUE; gBattleStruct->moveDamage[partner] = (GetNonDynamaxMaxHP(partner) / 4) * -1; BattleScriptPushCursorAndCallback(BattleScript_HospitalityActivates); @@ -4074,10 +4077,9 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && gBattleMons[battler].species == SPECIES_TERAPAGOS_NORMAL && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_SWITCH)) { - gBattlerAttacker = battler; gBattleScripting.abilityPopupOverwrite = gLastUsedAbility = ABILITY_TERA_SHIFT; gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_AttackerFormChangeWithStringEnd3); + BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeWithStringEnd3); effect++; } break; @@ -4296,8 +4298,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_SHIELDS_DOWN: if (TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT)) { - gBattlerAttacker = battler; - BattleScriptPushCursorAndCallback(BattleScript_AttackerFormChangeEnd3); + gBattleScripting.battler = battler; + BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeEnd3); effect++; } break; @@ -4328,8 +4330,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && GetActiveGimmick(battler) != GIMMICK_TERA && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_TURN_END)) { - gBattlerAttacker = battler; - BattleScriptPushCursorAndCallback(BattleScript_AttackerFormChangeEnd3NoPopup); + gBattleScripting.battler = battler; + BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeEnd3NoPopup); effect++; } break; @@ -4979,8 +4981,9 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && ((gCurrentMove == MOVE_SURF && IsBattlerTurnDamaged(gBattlerTarget)) || gStatuses3[gBattlerAttacker] & STATUS3_UNDERWATER) && TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_HP_PERCENT)) { + gBattleScripting.battler = gBattlerAttacker; BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_AttackerFormChange; + gBattlescriptCurrInstr = BattleScript_BattlerFormChange; effect++; } break; From ac69a61cb4060bcd3df09161447710b558c6818d Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Fri, 18 Jul 2025 10:57:32 -0400 Subject: [PATCH 15/17] Fix magnet rise ai timer check (#7361) Co-authored-by: ghoulslash --- src/battle_ai_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index b785180bc2..96265621b2 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -2769,7 +2769,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_MAGNET_RISE: if (gFieldStatuses & STATUS_FIELD_GRAVITY - || gDisableStructs[battlerAtk].magnetRiseTimer != 0 + || gDisableStructs[battlerAtk].magnetRiseTimer > gBattleTurnCounter || aiData->holdEffects[battlerAtk] == HOLD_EFFECT_IRON_BALL || gStatuses3[battlerAtk] & (STATUS3_ROOTED | STATUS3_MAGNET_RISE | STATUS3_SMACKED_DOWN) || !IsBattlerGrounded(battlerAtk)) From c61c2bbbc1b59a26f5a0b75f3b25336d99d5f35a Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Sat, 19 Jul 2025 03:56:56 -0400 Subject: [PATCH 16/17] Fix AI unusable move scoring freeze (#7369) --- src/battle_ai_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 96265621b2..5c0c0b0590 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -290,7 +290,7 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) { if (moveLimitations & (1u << moveIndex)) SET_SCORE(battler, moveIndex, 0); - if (defaultScoreMoves & 1) + else if (defaultScoreMoves & 1) SET_SCORE(battler, moveIndex, AI_SCORE_DEFAULT); else SET_SCORE(battler, moveIndex, 0); From c6f539c8be1f6f671e8ce356e7731b699e7263c1 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sun, 20 Jul 2025 01:21:10 +0200 Subject: [PATCH 17/17] Adds Missing break in ABILITY_TERAFORM_ZERO (#7377) --- src/battle_util.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/battle_util.c b/src/battle_util.c index f3972791f8..b5e68f9cbd 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3871,6 +3871,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 BattleScriptPushCursorAndCallback(BattleScript_ActivateTeraformZero); effect++; } + break; case ABILITY_SCHOOLING: if (gBattleMons[battler].level < 20) break;