From c549e29c2e3121580436c930ff4a151912221cb5 Mon Sep 17 00:00:00 2001 From: Martin Griffin Date: Fri, 21 Nov 2025 19:20:53 +0000 Subject: [PATCH 01/19] *.party: text with lfs --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 1f2aa0c6da..8c5e457ed6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18,6 +18,7 @@ Makefile text eol=lf *.md text eol=lf *.ps1 text eol=crlf *.yml text eol=lf +*.party text eol=lf *.png binary *.bin binary From ee09f27e2c3da0c09b222cbb458ad762663cbc1e Mon Sep 17 00:00:00 2001 From: Martin Griffin Date: Fri, 21 Nov 2025 19:30:30 +0000 Subject: [PATCH 02/19] squash! *.party: text with lfs cfg and ld too --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index 8c5e457ed6..a2dcec5e5d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,6 +19,8 @@ Makefile text eol=lf *.ps1 text eol=crlf *.yml text eol=lf *.party text eol=lf +*.cfg text eol=lf +*.ld text eol=lf *.png binary *.bin binary From 8ba99b09d78b5b54b1420e8133f613c232415b80 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sat, 22 Nov 2025 07:11:22 +0100 Subject: [PATCH 03/19] Fix game freeze when trainers try to walk on sideway stairs (#8316) --- src/trainer_see.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/trainer_see.c b/src/trainer_see.c index 348ec14c2d..4ac9c513b2 100644 --- a/src/trainer_see.c +++ b/src/trainer_see.c @@ -522,6 +522,9 @@ static u8 GetTrainerApproachDistance(struct ObjectEvent *trainerObj) PlayerGetDestCoords(&x, &y); if (trainerObj->trainerType == TRAINER_TYPE_NORMAL) // can only see in one direction { + // Disable trainer approach while moving diagonally (usually moving on sideway stairs) + if (trainerObj->facingDirection > DIR_EAST) + return 0; approachDistance = sDirectionalApproachDistanceFuncs[trainerObj->facingDirection - 1](trainerObj, trainerObj->trainerRange_berryTreeId, x, y); return CheckPathBetweenTrainerAndPlayer(trainerObj, approachDistance, trainerObj->facingDirection); } From 29ac028c9ec3ae5fee7295d9f7ccef76d9785df0 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sat, 22 Nov 2025 07:13:15 +0100 Subject: [PATCH 04/19] Fix tossing items applying to the wrong stack (#8282) --- include/item.h | 1 + src/item.c | 7 +++++++ src/item_menu.c | 28 ++++++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/include/item.h b/include/item.h index 1f3b1fab1b..56fe152720 100644 --- a/include/item.h +++ b/include/item.h @@ -215,6 +215,7 @@ bool32 CheckBagHasSpace(u16 itemId, u16 count); u32 GetFreeSpaceForItemInBag(u16 itemId); bool32 AddBagItem(u16 itemId, u16 count); bool32 RemoveBagItem(u16 itemId, u16 count); +void RemoveBagItemFromSlot(struct BagPocket *pocket, u16 slotId, u16 count); u8 CountUsedPCItemSlots(void); bool32 CheckPCHasItem(u16 itemId, u16 count); bool32 AddPCItem(u16 itemId, u16 count); diff --git a/src/item.c b/src/item.c index 7519060287..5bfc59c68f 100644 --- a/src/item.c +++ b/src/item.c @@ -411,6 +411,13 @@ bool32 RemoveBagItem(u16 itemId, u16 count) return BagPocket_RemoveItem(&gBagPockets[GetItemPocket(itemId)], itemId, count); } +// Unsafe function: Only use with functions that already check the slot and count are valid +void RemoveBagItemFromSlot(struct BagPocket *pocket, u16 slotId, u16 count) +{ + struct ItemSlot itemSlot = BagPocket_GetSlotData(pocket, slotId); + BagPocket_SetSlotItemIdAndCount(pocket, slotId, itemSlot.itemId, itemSlot.quantity - count); +} + static u8 NONNULL BagPocket_CountUsedItemSlots(struct BagPocket *pocket) { u8 usedSlots = 0; diff --git a/src/item_menu.c b/src/item_menu.c index f19cad0cdc..e63758b3ac 100755 --- a/src/item_menu.c +++ b/src/item_menu.c @@ -170,6 +170,7 @@ static void PrintThereIsNoPokemon(u8); static void Task_ChooseHowManyToToss(u8); static void AskTossItems(u8); static void Task_RemoveItemFromBag(u8); +static void Task_TossItemFromBag(u8 taskId); static void ItemMenu_Cancel(u8); static void HandleErrorMessage(u8); static void PrintItemCantBeHeld(u8); @@ -1958,11 +1959,34 @@ static void ConfirmToss(u8 taskId) StringExpandPlaceholders(gStringVar4, gText_ThrewAwayVar2Var1s); FillWindowPixelBuffer(WIN_DESCRIPTION, PIXEL_FILL(0)); BagMenu_Print(WIN_DESCRIPTION, FONT_NORMAL, gStringVar4, 3, 1, 0, 0, 0, COLORID_NORMAL); - gTasks[taskId].func = Task_RemoveItemFromBag; + if (CurrentBattlePyramidLocation() != PYRAMID_LOCATION_NONE || FlagGet(FLAG_STORING_ITEMS_IN_PYRAMID_BAG) == TRUE) + gTasks[taskId].func = Task_RemoveItemFromBag; + else + gTasks[taskId].func = Task_TossItemFromBag; +} + +static void Task_TossItemFromBag(u8 taskId) +{ + s16 *data = gTasks[taskId].data; + u16 *scrollPos = &gBagPosition.scrollPosition[gBagPosition.pocket]; + u16 *cursorPos = &gBagPosition.cursorPosition[gBagPosition.pocket]; + + if (JOY_NEW(A_BUTTON | B_BUTTON)) + { + PlaySE(SE_SELECT); + RemoveBagItemFromSlot(&gBagPockets[gBagPosition.pocket], *scrollPos + *cursorPos, tItemCount); + DestroyListMenuTask(tListTaskId, scrollPos, cursorPos); + UpdatePocketItemList(gBagPosition.pocket); + UpdatePocketListPosition(gBagPosition.pocket); + LoadBagItemListBuffers(gBagPosition.pocket); + tListTaskId = ListMenuInit(&gMultiuseListMenuTemplate, *scrollPos, *cursorPos); + ScheduleBgCopyTilemapToVram(0); + ReturnToItemList(taskId); + } } // Remove selected item(s) from the bag and update list -// For when items are tossed or deposited +// For when items are deposited static void Task_RemoveItemFromBag(u8 taskId) { s16 *data = gTasks[taskId].data; From 073fe375a35000af3fd7587d66a3d9a209fce3e1 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sat, 22 Nov 2025 07:13:54 +0100 Subject: [PATCH 05/19] Tests for Max Moves already exist (#8314) --- test/battle/move_effect/max_move.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/battle/move_effect/max_move.c b/test/battle/move_effect/max_move.c index 41c2a4e32c..38924f1d33 100644 --- a/test/battle/move_effect/max_move.c +++ b/test/battle/move_effect/max_move.c @@ -1,4 +1,4 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Max Moves (Move Effect) test titles") +// Tested in the dynamax.c test file From 9b0a36e1d3b2d97119822977e3136f0bacd9ae4a Mon Sep 17 00:00:00 2001 From: Estellar <137097857+estellarc@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:56:27 -0300 Subject: [PATCH 06/19] Use MAP_OFFSET (#8328) --- src/dexnav.c | 4 ++-- src/field_specials.c | 4 ++-- src/scrcmd.c | 4 ++-- src/vs_seeker.c | 9 +++++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/dexnav.c b/src/dexnav.c index 581178e940..0efcf110aa 100644 --- a/src/dexnav.c +++ b/src/dexnav.c @@ -932,8 +932,8 @@ static void DexNavUpdateDirectionArrow(void) { u16 tileX = sDexNavSearchDataPtr->tileX; u16 tileY = sDexNavSearchDataPtr->tileY; - u16 playerX = gSaveBlock1Ptr->pos.x + 7; - u16 playerY = gSaveBlock1Ptr->pos.y + 7; + u16 playerX = gSaveBlock1Ptr->pos.x + MAP_OFFSET; + u16 playerY = gSaveBlock1Ptr->pos.y + MAP_OFFSET; u16 deltaX = abs(tileX - playerX); u16 deltaY = abs(tileY - playerY); const u8 *str; diff --git a/src/field_specials.c b/src/field_specials.c index cd7a2a798f..9fc560916d 100644 --- a/src/field_specials.c +++ b/src/field_specials.c @@ -4308,8 +4308,8 @@ void GetObjectPosition(u16* xPointer, u16* yPointer, u32 localId, u32 useTemplat objectId = GetObjectEventIdByLocalId(localId); objEvent = &gObjectEvents[objectId]; - *xPointer = objEvent->currentCoords.x - 7; - *yPointer = objEvent->currentCoords.y - 7; + *xPointer = objEvent->currentCoords.x - MAP_OFFSET; + *yPointer = objEvent->currentCoords.y - MAP_OFFSET; } bool32 CheckObjectAtXY(u32 x, u32 y) diff --git a/src/scrcmd.c b/src/scrcmd.c index 54edd76c61..40ed11a9d0 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -3085,8 +3085,8 @@ bool8 ScrCmd_getobjectxy(struct ScriptContext *ctx) bool8 ScrCmd_checkobjectat(struct ScriptContext *ctx) { - u32 x = VarGet(ScriptReadHalfword(ctx)) + 7; - u32 y = VarGet(ScriptReadHalfword(ctx)) + 7; + u32 x = VarGet(ScriptReadHalfword(ctx)) + MAP_OFFSET; + u32 y = VarGet(ScriptReadHalfword(ctx)) + MAP_OFFSET; u32 varId = ScriptReadHalfword(ctx); Script_RequestEffects(SCREFF_V1); diff --git a/src/vs_seeker.c b/src/vs_seeker.c index d9e4497421..48bdbc2abb 100644 --- a/src/vs_seeker.c +++ b/src/vs_seeker.c @@ -15,6 +15,7 @@ #include "battle_setup.h" #include "random.h" #include "field_player_avatar.h" +#include "fieldmap.h" #include "vs_seeker.h" #include "menu.h" #include "string_util.h" @@ -390,8 +391,8 @@ static void GatherNearbyTrainerInfo(void) sVsSeeker->trainerInfo[vsSeekerObjectIdx].localId = templates[objectEventIdx].localId; TryGetObjectEventIdByLocalIdAndMap(templates[objectEventIdx].localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, &objectEventId); sVsSeeker->trainerInfo[vsSeekerObjectIdx].objectEventId = objectEventId; - sVsSeeker->trainerInfo[vsSeekerObjectIdx].xCoord = gObjectEvents[objectEventId].currentCoords.x - 7; - sVsSeeker->trainerInfo[vsSeekerObjectIdx].yCoord = gObjectEvents[objectEventId].currentCoords.y - 7; + sVsSeeker->trainerInfo[vsSeekerObjectIdx].xCoord = gObjectEvents[objectEventId].currentCoords.x - MAP_OFFSET; + sVsSeeker->trainerInfo[vsSeekerObjectIdx].yCoord = gObjectEvents[objectEventId].currentCoords.y - MAP_OFFSET; sVsSeeker->trainerInfo[vsSeekerObjectIdx].graphicsId = templates[objectEventIdx].graphicsId; vsSeekerObjectIdx++; } @@ -766,8 +767,8 @@ static bool8 IsTrainerVisibleOnScreen(struct VsSeekerTrainerInfo * trainerInfo) s16 y; PlayerGetDestCoords(&x, &y); - x -= 7; - y -= 7; + x -= MAP_OFFSET; + y -= MAP_OFFSET; if ( x - 7 <= trainerInfo->xCoord && x + 7 >= trainerInfo->xCoord From 7c20fbd76b7d1e687bc080247ce6a4ae52a71e56 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sun, 23 Nov 2025 10:27:55 +0100 Subject: [PATCH 07/19] Fix Kings Rock not being ignored by flinch moves (#8327) --- src/battle_util.c | 10 +++--- test/battle/hold_effect/kings_rock.c | 36 ++++++++++++++++++++++ test/battle/move_effect_secondary/flinch.c | 14 +++++++++ 3 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 test/battle/hold_effect/kings_rock.c diff --git a/src/battle_util.c b/src/battle_util.c index 8266c22f76..2741d37c43 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -6934,17 +6934,17 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler) switch (atkHoldEffect) { case HOLD_EFFECT_FLINCH: + if (!MoveIgnoresKingsRock(gCurrentMove) + && !MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_FLINCH) + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(gBattlerTarget)) { u16 ability = GetBattlerAbility(gBattlerAttacker); if (B_SERENE_GRACE_BOOST >= GEN_5 && ability == ABILITY_SERENE_GRACE) atkHoldEffectParam *= 2; if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_RAINBOW && gCurrentMove != MOVE_SECRET_POWER) atkHoldEffectParam *= 2; - if (IsBattlerTurnDamaged(gBattlerTarget) - && !MoveIgnoresKingsRock(gCurrentMove) - && gBattleMons[gBattlerTarget].hp - && RandomPercentage(RNG_HOLD_EFFECT_FLINCH, atkHoldEffectParam) - && ability != ABILITY_STENCH) + if (ability != ABILITY_STENCH && RandomPercentage(RNG_HOLD_EFFECT_FLINCH, atkHoldEffectParam)) { gBattleScripting.moveEffect = MOVE_EFFECT_FLINCH; BattleScriptPushCursor(); diff --git a/test/battle/hold_effect/kings_rock.c b/test/battle/hold_effect/kings_rock.c new file mode 100644 index 0000000000..9d396391fc --- /dev/null +++ b/test/battle/hold_effect/kings_rock.c @@ -0,0 +1,36 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gItemsInfo[ITEM_KINGS_ROCK].holdEffect == HOLD_EFFECT_FLINCH); +} + +SINGLE_BATTLE_TEST("Kings Rock holder will flinch the target 10% of the time") +{ + PASSES_RANDOMLY(10, 100, RNG_HOLD_EFFECT_FLINCH); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_KINGS_ROCK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } +} + +SINGLE_BATTLE_TEST("Kings Rock does not increase flinch chance of a move that has the flinch effect") +{ + PASSES_RANDOMLY(30, 100, RNG_SECONDARY_EFFECT); + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_HEADBUTT, MOVE_EFFECT_FLINCH)); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_KINGS_ROCK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HEADBUTT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, player); + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } +} diff --git a/test/battle/move_effect_secondary/flinch.c b/test/battle/move_effect_secondary/flinch.c index 517c87cba9..41c1910b4f 100644 --- a/test/battle/move_effect_secondary/flinch.c +++ b/test/battle/move_effect_secondary/flinch.c @@ -69,3 +69,17 @@ SINGLE_BATTLE_TEST("Protect always works when used after flinching") NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, opponent); } } + +SINGLE_BATTLE_TEST("Headbutt flinches 30% of the time") +{ + PASSES_RANDOMLY(30, 100, RNG_SECONDARY_EFFECT); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HEADBUTT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, player); + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } +} From 51a750e61a197a95c5dcf642c9bac42be72eae11 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Sun, 23 Nov 2025 08:04:36 -0300 Subject: [PATCH 08/19] Finished fixing tests when setting `GEN_LATEST` to `GEN_5` (#8263) --- include/constants/generational_changes.h | 5 +++ include/generational_changes.h | 5 +++ src/battle_ai_util.c | 2 +- src/battle_script_commands.c | 10 +++--- src/battle_util.c | 4 +-- src/data/moves_info.h | 2 +- test/battle/ability/gulp_missile.c | 2 +- test/battle/ability/mirror_armor.c | 16 +++++---- test/battle/ability/synchronize.c | 1 + test/battle/ai/ai.c | 9 +++++ test/battle/ai/ai_check_viability.c | 12 ++++--- test/battle/ai/ai_doubles.c | 22 ++++++------ test/battle/ai/ai_switching.c | 7 +++- test/battle/damage_formula.c | 3 +- test/battle/gimmick/dynamax.c | 3 +- test/battle/gimmick/zmove.c | 10 ++++-- test/battle/hold_effect/safety_goggles.c | 2 ++ test/battle/move_effect/defog.c | 36 ++++++++++++++----- test/battle/move_effect/focus_punch.c | 3 ++ test/battle/move_effect/glaive_rush.c | 6 ++-- test/battle/move_effect/last_respects.c | 4 +-- test/battle/move_effect/protect.c | 22 +++++++----- test/battle/move_effect/speed_down.c | 1 + test/battle/move_effect/upper_hand.c | 2 ++ test/battle/move_effect_secondary/dire_claw.c | 2 ++ 25 files changed, 133 insertions(+), 58 deletions(-) diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 381c77ca13..52c7401afe 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -48,6 +48,11 @@ enum GenConfigTag GEN_CONFIG_PARALYZE_ELECTRIC, GEN_CONFIG_BADGE_BOOST, GEN_CONFIG_LEAF_GUARD_PREVENTS_REST, + GEN_CONFIG_WIDE_GUARD, + GEN_CONFIG_QUICK_GUARD, + GEN_CONFIG_DEFOG_EFFECT_CLEARING, + GEN_CONFIG_BURN_HIT_THAW, + GEN_CONFIG_BURN_FACADE_DMG, GEN_CONFIG_COUNT }; diff --git a/include/generational_changes.h b/include/generational_changes.h index 0068d74428..dc612e9d64 100644 --- a/include/generational_changes.h +++ b/include/generational_changes.h @@ -51,6 +51,11 @@ static const u8 sGenerationalChanges[GEN_CONFIG_COUNT] = [GEN_CONFIG_PARALYZE_ELECTRIC] = B_PARALYZE_ELECTRIC, [GEN_CONFIG_BADGE_BOOST] = B_BADGE_BOOST, [GEN_CONFIG_LEAF_GUARD_PREVENTS_REST] = B_LEAF_GUARD_PREVENTS_REST, + [GEN_CONFIG_WIDE_GUARD] = B_WIDE_GUARD, + [GEN_CONFIG_QUICK_GUARD] = B_QUICK_GUARD, + [GEN_CONFIG_DEFOG_EFFECT_CLEARING] = B_DEFOG_EFFECT_CLEARING, + [GEN_CONFIG_BURN_HIT_THAW] = B_BURN_HIT_THAW, + [GEN_CONFIG_BURN_FACADE_DMG] = B_BURN_FACADE_DMG, }; #if TESTING diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index e39e80a2d2..904d8770e9 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -1897,7 +1897,7 @@ bool32 IsHazardClearingMove(u32 move) case EFFECT_TIDY_UP: return TRUE; case EFFECT_DEFOG: - if (B_DEFOG_EFFECT_CLEARING >= GEN_6) + if (GetGenConfig(GEN_CONFIG_DEFOG_EFFECT_CLEARING) >= GEN_6) return TRUE; break; } diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index c45c3cc9a5..319bee99e1 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -9322,7 +9322,7 @@ static bool32 TryDefogClear(u32 battlerAtk, bool32 clear) DEFOG_CLEAR(SIDE_STATUS_AURORA_VEIL, auroraVeilTimer, BattleScript_SideStatusWoreOffReturn, MOVE_AURORA_VEIL); DEFOG_CLEAR(SIDE_STATUS_SAFEGUARD, safeguardTimer, BattleScript_SideStatusWoreOffReturn, MOVE_SAFEGUARD); } - if (B_DEFOG_EFFECT_CLEARING >= GEN_6) + if (GetGenConfig(GEN_CONFIG_DEFOG_EFFECT_CLEARING) >= GEN_6) { gBattlerAttacker = i; // For correct battle string. Ally's / Foe's if (DefogClearHazards(saveBattler, i, clear)) @@ -9334,7 +9334,7 @@ static bool32 TryDefogClear(u32 battlerAtk, bool32 clear) BattleScriptCall(BattleScript_FogEnded_Ret); return TRUE; } - if (B_DEFOG_EFFECT_CLEARING >= GEN_8 && (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)) + if (GetGenConfig(GEN_CONFIG_DEFOG_EFFECT_CLEARING) >= GEN_8 && (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)) { RemoveAllTerrains(); BattleScriptCall(BattleScript_TerrainEnds_Ret); @@ -9653,8 +9653,8 @@ static void Cmd_setprotectlike(void) notLastTurn = FALSE; if ((sProtectSuccessRates[gDisableStructs[gBattlerAttacker].protectUses] >= RandomUniform(RNG_PROTECT_FAIL, 0, USHRT_MAX) && notLastTurn) - || (protectMethod == PROTECT_WIDE_GUARD && B_WIDE_GUARD != GEN_5) - || (protectMethod == PROTECT_QUICK_GUARD && B_QUICK_GUARD != GEN_5)) + || (protectMethod == PROTECT_WIDE_GUARD && GetGenConfig(GEN_CONFIG_WIDE_GUARD) >= GEN_6) + || (protectMethod == PROTECT_QUICK_GUARD && GetGenConfig(GEN_CONFIG_QUICK_GUARD) >= GEN_6)) { if (GetMoveEffect(gCurrentMove) == EFFECT_ENDURE) { @@ -14971,7 +14971,7 @@ bool32 CanBurnHitThaw(u16 move) { u8 i; - if (B_BURN_HIT_THAW >= GEN_6) + if (GetGenConfig(GEN_CONFIG_BURN_HIT_THAW) >= GEN_6) { u32 numAdditionalEffects = GetMoveAdditionalEffectCount(move); for (i = 0; i < numAdditionalEffects; i++) diff --git a/src/battle_util.c b/src/battle_util.c index 2741d37c43..b30c6960b9 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -9025,12 +9025,12 @@ static inline uq4_12_t GetBurnOrFrostBiteModifier(struct DamageContext *ctx) if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_BURN && IsBattleMovePhysical(ctx->move) - && (B_BURN_FACADE_DMG < GEN_6 || moveEffect != EFFECT_FACADE) + && (GetGenConfig(GEN_CONFIG_BURN_FACADE_DMG) < GEN_6 || moveEffect != EFFECT_FACADE) && ctx->abilityAtk != ABILITY_GUTS) return UQ_4_12(0.5); if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_FROSTBITE && IsBattleMoveSpecial(ctx->move) - && (B_BURN_FACADE_DMG < GEN_6 || moveEffect != EFFECT_FACADE)) + && (GetGenConfig(GEN_CONFIG_BURN_FACADE_DMG) < GEN_6 || moveEffect != EFFECT_FACADE)) return UQ_4_12(0.5); return UQ_4_12(1.0); } diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 162a408528..e03db10286 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -5720,7 +5720,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .ignoresProtect = TRUE, .ignoresSubstitute = TRUE, .mirrorMoveBanned = TRUE, - .soundMove = B_UPDATED_MOVE_FLAGS != GEN_5, + .soundMove = TRUE, .contestEffect = CONTEST_EFFECT_BETTER_IF_LAST, .contestCategory = CONTEST_CATEGORY_BEAUTY, .contestComboStarterId = COMBO_STARTER_HEAL_BELL, diff --git a/test/battle/ability/gulp_missile.c b/test/battle/ability/gulp_missile.c index fe1bc718fd..13a684dc8c 100644 --- a/test/battle/ability/gulp_missile.c +++ b/test/battle/ability/gulp_missile.c @@ -143,7 +143,7 @@ SINGLE_BATTLE_TEST("(Gulp Missile) Transformed Cramorant Gulping lowers defense PARAMETRIZE { species = SPECIES_CHATOT; ability = ABILITY_BIG_PECKS; } GIVEN { PLAYER(SPECIES_CRAMORANT) { Ability(ABILITY_GULP_MISSILE); } - OPPONENT(species) { Ability(ability); } + OPPONENT(species) { Ability(ability); HP(9999); MaxHP(9999); } // In Gen 5 data, Surf would be enough to knock out Chatot } WHEN { TURN { MOVE(player, MOVE_SURF); MOVE(opponent, MOVE_SCRATCH); } } SCENE { diff --git a/test/battle/ability/mirror_armor.c b/test/battle/ability/mirror_armor.c index 7d027398f3..771df02699 100644 --- a/test/battle/ability/mirror_armor.c +++ b/test/battle/ability/mirror_armor.c @@ -5,12 +5,12 @@ SINGLE_BATTLE_TEST("Mirror Armor lowers a stat of the attacking Pokémon") { u16 move, statId; - PARAMETRIZE { move = MOVE_LEER; statId = STAT_DEF; } - PARAMETRIZE { move = MOVE_GROWL; statId = STAT_ATK; } + PARAMETRIZE { move = MOVE_LEER; statId = STAT_DEF; } + PARAMETRIZE { move = MOVE_GROWL; statId = STAT_ATK; } PARAMETRIZE { move = MOVE_SWEET_SCENT; statId = STAT_EVASION; } PARAMETRIZE { move = MOVE_SAND_ATTACK; statId = STAT_ACC; } - PARAMETRIZE { move = MOVE_CONFIDE; statId = STAT_SPATK; } - PARAMETRIZE { move = MOVE_FAKE_TEARS; statId = STAT_SPDEF; } + PARAMETRIZE { move = MOVE_CONFIDE; statId = STAT_SPATK; } + PARAMETRIZE { move = MOVE_FAKE_TEARS; statId = STAT_SPDEF; } GIVEN { PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR);} @@ -29,7 +29,11 @@ SINGLE_BATTLE_TEST("Mirror Armor lowers a stat of the attacking Pokémon") MESSAGE("The opposing Wynaut's Attack fell!"); break; case STAT_EVASION: - MESSAGE("The opposing Wynaut's evasiveness harshly fell!"); + if (GetMoveEffect(move) == EFFECT_EVASION_DOWN_2) { + MESSAGE("The opposing Wynaut's evasiveness harshly fell!"); + } else { + MESSAGE("The opposing Wynaut's evasiveness fell!"); + } break; case STAT_ACC: MESSAGE("The opposing Wynaut's accuracy fell!"); @@ -43,7 +47,7 @@ SINGLE_BATTLE_TEST("Mirror Armor lowers a stat of the attacking Pokémon") } } THEN { EXPECT_EQ(player->statStages[statId], DEFAULT_STAT_STAGE); - EXPECT_EQ(opponent->statStages[statId], (statId == STAT_SPDEF || statId == STAT_EVASION) ? DEFAULT_STAT_STAGE - 2 : DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponent->statStages[statId], (statId == STAT_SPDEF || (statId == STAT_EVASION && GetMoveEffect(move) == EFFECT_EVASION_DOWN_2)) ? DEFAULT_STAT_STAGE - 2 : DEFAULT_STAT_STAGE - 1); } } diff --git a/test/battle/ability/synchronize.c b/test/battle/ability/synchronize.c index e150e2950e..fbdb3b21c0 100644 --- a/test/battle/ability/synchronize.c +++ b/test/battle/ability/synchronize.c @@ -25,6 +25,7 @@ SINGLE_BATTLE_TEST("Synchronize will mirror back non volatile status back at opp SINGLE_BATTLE_TEST("Synchronize will still show up the ability pop up even if it fails") { GIVEN { + WITH_CONFIG(GEN_CONFIG_PARALYZE_ELECTRIC, GEN_6); ASSUME(MoveMakesContact(MOVE_TACKLE)); PLAYER(SPECIES_PIKACHU) { Ability(ABILITY_STATIC); } OPPONENT(SPECIES_ABRA) { Ability(ABILITY_SYNCHRONIZE); } diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index 3e8f45717e..c3b69b9c4c 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -10,6 +10,7 @@ AI_SINGLE_BATTLE_TEST("AI prefers Bubble over Water Gun if it's slower") PARAMETRIZE { speedPlayer = 10; speedAi = 200; } GIVEN { + ASSUME(GetMovePower(MOVE_WATER_GUN) == GetMovePower(MOVE_BUBBLE)); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_SCIZOR) { Speed(speedPlayer); } OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_WATER_GUN, MOVE_BUBBLE); Speed(speedAi); } @@ -34,6 +35,7 @@ AI_SINGLE_BATTLE_TEST("AI prefers Water Gun over Bubble if it knows that foe has PARAMETRIZE { abilityAI = ABILITY_MOXIE; } PARAMETRIZE { abilityAI = ABILITY_MOLD_BREAKER; } // Mold Breaker ignores Contrary. GIVEN { + ASSUME(GetMovePower(MOVE_BUBBLE) == GetMovePower(MOVE_WATER_GUN)); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_SHUCKLE) { Ability(ABILITY_CONTRARY); } OPPONENT(SPECIES_PINSIR) { Moves(MOVE_WATER_GUN, MOVE_BUBBLE); Ability(abilityAI); } @@ -150,6 +152,7 @@ AI_SINGLE_BATTLE_TEST("AI prefers moves which deal more damage instead of moves ASSUME(GetMoveCategory(MOVE_SCALD) == DAMAGE_CATEGORY_SPECIAL); ASSUME(GetMoveCategory(MOVE_POISON_JAB) == DAMAGE_CATEGORY_PHYSICAL); ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetSpeciesBaseAttack(SPECIES_NIDOQUEEN) == 92); // Gen 5's 82 Base Attack causes the test to fail AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_TYPHLOSION) { Ability(abilityDef); } PLAYER(SPECIES_WOBBUFFET); @@ -204,6 +207,9 @@ AI_SINGLE_BATTLE_TEST("AI prefers a weaker move over a one with a downside effec GIVEN { ASSUME(GetMoveCategory(MOVE_FLAMETHROWER) == DAMAGE_CATEGORY_SPECIAL); // Added because Typhlosion has to KO Wobbuffet ASSUME(GetMoveCategory(MOVE_OVERHEAT) == DAMAGE_CATEGORY_SPECIAL); // Added because Typhlosion has to KO Wobbuffet + // With Gen 5 data, it chooses Overheat instead + ASSUME(GetMovePower(MOVE_FLAMETHROWER) == 90); // In Gen 5, it's 95 + ASSUME(GetMovePower(MOVE_OVERHEAT) == 130); // In Gen 5, it's 140. AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET) { HP(hp); } PLAYER(SPECIES_WOBBUFFET); @@ -332,6 +338,7 @@ AI_SINGLE_BATTLE_TEST("AI won't use Solar Beam if there is no Sun up or the user GIVEN { ASSUME(GetMoveCategory(MOVE_SOLAR_BEAM) == DAMAGE_CATEGORY_SPECIAL); ASSUME(GetMoveCategory(MOVE_GRASS_PLEDGE) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMovePower(MOVE_GRASS_PLEDGE) == 80); // Gen 5's 50 power causes the test to fail AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET) { HP(211); } PLAYER(SPECIES_WOBBUFFET); @@ -687,6 +694,7 @@ AI_SINGLE_BATTLE_TEST("AI won't use thawing moves if target is frozen unless it PARAMETRIZE { status = STATUS1_FROSTBITE; aiMove = MOVE_EMBER; aiFlags = AI_FLAG_CHECK_BAD_MOVE; } GIVEN { + WITH_CONFIG(GEN_CONFIG_BURN_HIT_THAW, GEN_6); // In Gen 5, non-Fire burning moves didn't cause thawing ASSUME(GetMoveType(MOVE_EMBER) == TYPE_FIRE); ASSUME(GetMoveCategory(MOVE_TACKLE) == DAMAGE_CATEGORY_PHYSICAL); ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL); @@ -928,6 +936,7 @@ AI_SINGLE_BATTLE_TEST("AI will see Magnitude damage") AI_SINGLE_BATTLE_TEST("AI will prefer resisted move over failing move") { GIVEN { + WITH_CONFIG(GEN_CONFIG_POWDER_GRASS, GEN_6); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY); PLAYER(SPECIES_ROSELIA) { Moves(MOVE_ABSORB); }; OPPONENT(SPECIES_GLOOM) { Moves(MOVE_MEGA_DRAIN, MOVE_STUN_SPORE, MOVE_LEECH_SEED, MOVE_SYNTHESIS); } diff --git a/test/battle/ai/ai_check_viability.c b/test/battle/ai/ai_check_viability.c index 4dff0e08ca..25f3a7763c 100644 --- a/test/battle/ai/ai_check_viability.c +++ b/test/battle/ai/ai_check_viability.c @@ -18,6 +18,7 @@ AI_SINGLE_BATTLE_TEST("AI sees increased base power of Facade") PARAMETRIZE { status1 = STATUS1_BURN; expectedMove = MOVE_FACADE; } GIVEN { + WITH_CONFIG(GEN_CONFIG_BURN_FACADE_DMG, GEN_6); ASSUME(GetMoveEffect(MOVE_FACADE) == EFFECT_FACADE); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET) { HP(60); } @@ -162,6 +163,7 @@ AI_SINGLE_BATTLE_TEST("AI can choose Counter or Mirror Coat if the predicted mov ASSUME(GetMoveEffect(MOVE_MIRROR_COAT) == EFFECT_MIRROR_COAT); ASSUME(GetMoveCategory(MOVE_STRENGTH) == DAMAGE_CATEGORY_PHYSICAL); ASSUME(GetMoveCategory(MOVE_POWER_GEM) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMovePower(MOVE_POWER_GEM) == 80); // Gen 5's 70 power causes the test to fail AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET) { Speed(1); } OPPONENT(SPECIES_WOBBUFFET) { HP(102); Speed(100); Moves(opponentMove, MOVE_STRENGTH); } @@ -199,18 +201,18 @@ AI_DOUBLE_BATTLE_TEST("AI chooses moves that cure self or partner") { u32 status1_0, status1_1, partnerAbility, move; - PARAMETRIZE { status1_0 = STATUS1_NONE; status1_1 = STATUS1_NONE; + PARAMETRIZE { status1_0 = STATUS1_NONE; status1_1 = STATUS1_NONE; move = MOVE_HEAL_BELL; partnerAbility = ABILITY_SCRAPPY; } - PARAMETRIZE { status1_0 = STATUS1_TOXIC_POISON; status1_1 = STATUS1_NONE; + PARAMETRIZE { status1_0 = STATUS1_TOXIC_POISON; status1_1 = STATUS1_NONE; move = MOVE_HEAL_BELL; partnerAbility = ABILITY_SCRAPPY; } PARAMETRIZE { status1_0 = STATUS1_NONE; status1_1 = STATUS1_PARALYSIS; move = MOVE_HEAL_BELL; partnerAbility = ABILITY_SCRAPPY; } - PARAMETRIZE { status1_0 = STATUS1_NONE; status1_1 = STATUS1_PARALYSIS; + PARAMETRIZE { status1_0 = STATUS1_NONE; status1_1 = STATUS1_PARALYSIS; move = MOVE_HEAL_BELL; partnerAbility = ABILITY_SOUNDPROOF; } - PARAMETRIZE { status1_0 = STATUS1_NONE; status1_1 = STATUS1_NONE; + PARAMETRIZE { status1_0 = STATUS1_NONE; status1_1 = STATUS1_NONE; move = MOVE_REFRESH; partnerAbility = ABILITY_SCRAPPY; } - PARAMETRIZE { status1_0 = STATUS1_TOXIC_POISON; status1_1 = STATUS1_NONE; + PARAMETRIZE { status1_0 = STATUS1_TOXIC_POISON; status1_1 = STATUS1_NONE; move = MOVE_REFRESH; partnerAbility = ABILITY_SCRAPPY; } diff --git a/test/battle/ai/ai_doubles.c b/test/battle/ai/ai_doubles.c index add853b6d4..24d40912c8 100644 --- a/test/battle/ai/ai_doubles.c +++ b/test/battle/ai/ai_doubles.c @@ -494,15 +494,15 @@ AI_DOUBLE_BATTLE_TEST("AI sets up weather for its ally") PARAMETRIZE { goodWeather = MOVE_HAIL; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } PARAMETRIZE { goodWeather = MOVE_SNOWSCAPE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } PARAMETRIZE { goodWeather = MOVE_SANDSTORM; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_SHORE_UP; } - PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; goodWeather = MOVE_SUNNY_DAY; badWeather = MOVE_RAIN_DANCE; weatherTrigger = MOVE_SOLAR_BEAM; } - PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; goodWeather = MOVE_RAIN_DANCE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_THUNDER; } - PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; goodWeather = MOVE_HAIL; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } - PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; goodWeather = MOVE_SNOWSCAPE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } - PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; goodWeather = MOVE_SANDSTORM; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_SHORE_UP; } GIVEN { @@ -525,13 +525,13 @@ AI_DOUBLE_BATTLE_TEST("AI sets up terrain for its ally") PARAMETRIZE { goodTerrain = MOVE_GRASSY_TERRAIN; badTerrain = MOVE_PSYCHIC_TERRAIN; terrainTrigger = MOVE_GRASSY_GLIDE; } PARAMETRIZE { goodTerrain = MOVE_MISTY_TERRAIN; badTerrain = MOVE_PSYCHIC_TERRAIN; terrainTrigger = MOVE_MISTY_EXPLOSION; } PARAMETRIZE { goodTerrain = MOVE_PSYCHIC_TERRAIN; badTerrain = MOVE_ELECTRIC_TERRAIN; terrainTrigger = MOVE_EXPANDING_FORCE; } - PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; goodTerrain = MOVE_ELECTRIC_TERRAIN; badTerrain = MOVE_PSYCHIC_TERRAIN; terrainTrigger = MOVE_RISING_VOLTAGE; } - PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; goodTerrain = MOVE_GRASSY_TERRAIN; badTerrain = MOVE_PSYCHIC_TERRAIN; terrainTrigger = MOVE_GRASSY_GLIDE; } - PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; goodTerrain = MOVE_MISTY_TERRAIN; badTerrain = MOVE_PSYCHIC_TERRAIN; terrainTrigger = MOVE_MISTY_EXPLOSION; } - PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; goodTerrain = MOVE_PSYCHIC_TERRAIN; badTerrain = MOVE_ELECTRIC_TERRAIN; terrainTrigger = MOVE_EXPANDING_FORCE; } GIVEN { @@ -555,6 +555,7 @@ AI_DOUBLE_BATTLE_TEST("AI uses After You to set up Trick Room") GIVEN { ASSUME(GetMoveEffect(MOVE_AFTER_YOU) == EFFECT_AFTER_YOU); ASSUME(GetMoveEffect(MOVE_TRICK_ROOM) == EFFECT_TRICK_ROOM); + ASSUME(IsHealingMove(MOVE_DRAINING_KISS)); // Doesn't have the Healing Move flag in Gen 5 AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE); PLAYER(SPECIES_WOBBUFFET) { Speed(4); } PLAYER(SPECIES_WOBBUFFET) { Speed(4); } @@ -582,6 +583,7 @@ AI_DOUBLE_BATTLE_TEST("AI uses Trick Room intelligently") GIVEN { ASSUME(GetMoveEffect(MOVE_AFTER_YOU) == EFFECT_AFTER_YOU); ASSUME(GetMoveEffect(MOVE_TRICK_ROOM) == EFFECT_TRICK_ROOM); + ASSUME(IsHealingMove(MOVE_DRAINING_KISS)); // Doesn't have the Healing Move flag in Gen 5 AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE); PLAYER(SPECIES_WOBBUFFET) { Speed(4); } PLAYER(SPECIES_WOBBUFFET) { Speed(speed); } @@ -611,7 +613,7 @@ AI_DOUBLE_BATTLE_TEST("AI uses Trick Room with both battlers on the turn it expi TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_TRICK_ROOM); NOT_EXPECT_MOVE(opponentRight, MOVE_TRICK_ROOM); } TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_TRICK_ROOM); NOT_EXPECT_MOVE(opponentRight, MOVE_TRICK_ROOM); } TURN { EXPECT_MOVE(opponentLeft, MOVE_TRICK_ROOM); EXPECT_MOVE(opponentRight, MOVE_TRICK_ROOM); } - } + } } AI_DOUBLE_BATTLE_TEST("AI uses Helping Hand if it's about to die") diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 14ea727f85..08262d8755 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -53,7 +53,7 @@ AI_SINGLE_BATTLE_TEST("AI sees on-field player ability correctly and does not se OPPONENT(SPECIES_BOMBIRDIER) {Level(42); Moves(MOVE_ROCK_SLIDE); } OPPONENT(SPECIES_IRON_THORNS) {Level(43); Moves(MOVE_SUPERCELL_SLAM, MOVE_ICE_PUNCH); } } WHEN { - TURN { + TURN { MOVE(player, MOVE_VOLT_SWITCH); SEND_OUT(player, 1); EXPECT_MOVE(opponent, MOVE_SCRATCH); @@ -636,6 +636,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has bee GIVEN { ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + ASSUME(GetMovePower(MOVE_AURA_SPHERE) == 80); // Gen 5's 90 power causes too much damage AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH, MOVE_CELEBRATE, MOVE_TOXIC, MOVE_AURA_SPHERE); } OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } @@ -939,6 +940,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will stay in if Encore'd into { GIVEN { ASSUME(GetMoveEffect(MOVE_ENCORE) == EFFECT_ENCORE); + ASSUME(GetSpeciesType(SPECIES_AZURILL, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_AZURILL, 1) == TYPE_FAIRY); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); PLAYER(SPECIES_AZURILL) { Moves(MOVE_SCRATCH, MOVE_ENCORE); } OPPONENT(SPECIES_ODDISH) { Moves(MOVE_ACID); } @@ -1328,6 +1331,8 @@ AI_SINGLE_BATTLE_TEST("AI_SMART_MON_CHOICES: AI sees its own weather setting abi AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will properly consider immunities when determining switchin type matchup") { GIVEN { + ASSUME(GetSpeciesType(SPECIES_WHIMSICOTT, 0) == TYPE_GRASS); + ASSUME(GetSpeciesType(SPECIES_WHIMSICOTT, 1) == TYPE_FAIRY); // Gen 5's pure Grass type makes the test fail AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); PLAYER(SPECIES_POLIWRATH) { Moves(MOVE_WATER_GUN, MOVE_KARATE_CHOP); } OPPONENT(SPECIES_ZIGZAGOON) { Level(1); Moves(MOVE_SCRATCH); } diff --git a/test/battle/damage_formula.c b/test/battle/damage_formula.c index 4a7e4ec1ef..ded8cbdde2 100644 --- a/test/battle/damage_formula.c +++ b/test/battle/damage_formula.c @@ -41,7 +41,7 @@ SINGLE_BATTLE_TEST("Damage calculation matches Gen5+") } } -SINGLE_BATTLE_TEST("Damage calculation matches Gen5+ (Muscle Band, crit)") +SINGLE_BATTLE_TEST("Damage calculation matches Gen6+ (Muscle Band, crit)") { s16 dmg; s16 expectedDamage; @@ -62,6 +62,7 @@ SINGLE_BATTLE_TEST("Damage calculation matches Gen5+ (Muscle Band, crit)") PARAMETRIZE { expectedDamage = 276; } PARAMETRIZE { expectedDamage = 268; } GIVEN { + WITH_CONFIG(GEN_CONFIG_CRIT_MULTIPLIER, GEN_6); ASSUME(GetMoveCategory(MOVE_ICE_FANG) == DAMAGE_CATEGORY_PHYSICAL); PLAYER(SPECIES_GLACEON) { Level(75); Attack(123); Item(ITEM_MUSCLE_BAND); } OPPONENT(SPECIES_GARCHOMP) { Defense(163); } diff --git a/test/battle/gimmick/dynamax.c b/test/battle/gimmick/dynamax.c index 5cf79e2949..c27e305c1c 100644 --- a/test/battle/gimmick/dynamax.c +++ b/test/battle/gimmick/dynamax.c @@ -860,7 +860,7 @@ SINGLE_BATTLE_TEST("Dynamax: Max Hailstorm sets up hail") MESSAGE("It started to hail!"); MESSAGE("The opposing Wobbuffet used Celebrate!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HAIL_CONTINUES); -#endif +#endif } } @@ -971,6 +971,7 @@ SINGLE_BATTLE_TEST("Dynamax: G-Max Stonesurge sets up Stealth Rocks") SINGLE_BATTLE_TEST("Dynamax: G-Max Steelsurge sets up sharp steel") { GIVEN { + WITH_CONFIG(GEN_CONFIG_DEFOG_EFFECT_CLEARING, GEN_6); ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_STEELSURGE, MOVE_EFFECT_STEELSURGE)); PLAYER(SPECIES_COPPERAJAH) { GigantamaxFactor(TRUE); } OPPONENT(SPECIES_WOBBUFFET); diff --git a/test/battle/gimmick/zmove.c b/test/battle/gimmick/zmove.c index 1744fd151b..52e111520a 100644 --- a/test/battle/gimmick/zmove.c +++ b/test/battle/gimmick/zmove.c @@ -115,10 +115,16 @@ SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_ALL_STATS_UP raises all of a battler's sta } } -SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_BOOST_CRITS raises a battler's critical hit ratio") +SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_BOOST_CRITS raises a battler's critical hit ratio by 2 stages") { - PASSES_RANDOMLY(1, 2, RNG_CRITICAL_HIT); + u32 genConfig = 0, chance; + for (u32 j = GEN_1; j <= GEN_5; j++) + PARAMETRIZE { genConfig = j; chance = 4; } // 25% + for (u32 j = GEN_6; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; chance = 2; } // 50% + PASSES_RANDOMLY(1, chance, RNG_CRITICAL_HIT); GIVEN { + WITH_CONFIG(GEN_CONFIG_CRIT_CHANCE, genConfig); ASSUME(GetMoveType(MOVE_FORESIGHT) == TYPE_NORMAL); ASSUME(GetMoveZEffect(MOVE_FORESIGHT) == Z_EFFECT_BOOST_CRITS); PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } diff --git a/test/battle/hold_effect/safety_goggles.c b/test/battle/hold_effect/safety_goggles.c index 346e41fbfe..1c867ad2e7 100644 --- a/test/battle/hold_effect/safety_goggles.c +++ b/test/battle/hold_effect/safety_goggles.c @@ -46,8 +46,10 @@ SINGLE_BATTLE_TEST("Safety Goggles blocks damage from Sandstorm") SINGLE_BATTLE_TEST("Safety Goggles blocks Effect Spore's effect") { + KNOWN_FAILING; PASSES_RANDOMLY(100, 100, RNG_EFFECT_SPORE); GIVEN { + WITH_CONFIG(GEN_CONFIG_POWDER_GRASS, GEN_5); // Setting it to Gen 6 causes it to pass ASSUME(MoveMakesContact(MOVE_SCRATCH)); PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SAFETY_GOGGLES); } OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_EFFECT_SPORE); } diff --git a/test/battle/move_effect/defog.c b/test/battle/move_effect/defog.c index 96b79438f1..b5e73f9406 100644 --- a/test/battle/move_effect/defog.c +++ b/test/battle/move_effect/defog.c @@ -334,7 +334,11 @@ DOUBLE_BATTLE_TEST("Defog removes Aurora Veil from target's side", s16 damagePhy DOUBLE_BATTLE_TEST("Defog removes everything it can") { + u32 config; + PARAMETRIZE { config = GEN_5; } + PARAMETRIZE { config = GEN_6; } GIVEN { + WITH_CONFIG(GEN_CONFIG_DEFOG_EFFECT_CLEARING, config); ASSUME(GetMoveEffect(MOVE_HAIL) == EFFECT_HAIL); ASSUME(GetSpeciesType(SPECIES_GLALIE, 0) == TYPE_ICE); PLAYER(SPECIES_GLALIE) { Speed(4); } @@ -363,7 +367,7 @@ DOUBLE_BATTLE_TEST("Defog removes everything it can") MESSAGE("Your team's Aurora Veil wore off!"); MESSAGE("Your team's Safeguard wore off!"); - if (B_DEFOG_EFFECT_CLEARING >= GEN_6) { + if (config == GEN_6) { MESSAGE("The spikes disappeared from the ground around your team!"); MESSAGE("The sticky web has disappeared from the ground around your team!"); MESSAGE("The poison spikes disappeared from the ground around your team!"); @@ -376,17 +380,31 @@ DOUBLE_BATTLE_TEST("Defog removes everything it can") MESSAGE("The pointed stones disappeared from around the opposing team!"); } } THEN { - EXPECT_EQ(gBattleStruct->hazardsQueue[0][0], HAZARDS_NONE); - EXPECT_EQ(gBattleStruct->hazardsQueue[0][1], HAZARDS_NONE); - EXPECT_EQ(gBattleStruct->hazardsQueue[0][2], HAZARDS_NONE); - EXPECT_EQ(gBattleStruct->hazardsQueue[0][3], HAZARDS_NONE); + if (config == GEN_6) { + EXPECT_EQ(gBattleStruct->hazardsQueue[0][0], HAZARDS_NONE); + EXPECT_EQ(gBattleStruct->hazardsQueue[0][1], HAZARDS_NONE); + EXPECT_EQ(gBattleStruct->hazardsQueue[0][2], HAZARDS_NONE); + EXPECT_EQ(gBattleStruct->hazardsQueue[0][3], HAZARDS_NONE); + } else { + EXPECT_EQ(gBattleStruct->hazardsQueue[0][0], HAZARDS_STICKY_WEB); + EXPECT_EQ(gBattleStruct->hazardsQueue[0][1], HAZARDS_SPIKES); + EXPECT_EQ(gBattleStruct->hazardsQueue[0][2], HAZARDS_STEALTH_ROCK); + EXPECT_EQ(gBattleStruct->hazardsQueue[0][3], HAZARDS_TOXIC_SPIKES); + } EXPECT_EQ(gBattleStruct->hazardsQueue[0][4], HAZARDS_NONE); EXPECT_EQ(gBattleStruct->hazardsQueue[0][5], HAZARDS_NONE); - EXPECT_EQ(gBattleStruct->hazardsQueue[1][0], HAZARDS_NONE); - EXPECT_EQ(gBattleStruct->hazardsQueue[1][1], HAZARDS_NONE); - EXPECT_EQ(gBattleStruct->hazardsQueue[1][2], HAZARDS_NONE); - EXPECT_EQ(gBattleStruct->hazardsQueue[1][3], HAZARDS_NONE); + if (config == GEN_6) { + EXPECT_EQ(gBattleStruct->hazardsQueue[1][0], HAZARDS_NONE); + EXPECT_EQ(gBattleStruct->hazardsQueue[1][1], HAZARDS_NONE); + EXPECT_EQ(gBattleStruct->hazardsQueue[1][2], HAZARDS_NONE); + EXPECT_EQ(gBattleStruct->hazardsQueue[1][3], HAZARDS_NONE); + } else { + EXPECT_EQ(gBattleStruct->hazardsQueue[1][0], HAZARDS_STICKY_WEB); + EXPECT_EQ(gBattleStruct->hazardsQueue[1][1], HAZARDS_SPIKES); + EXPECT_EQ(gBattleStruct->hazardsQueue[1][2], HAZARDS_TOXIC_SPIKES); + EXPECT_EQ(gBattleStruct->hazardsQueue[1][3], HAZARDS_STEALTH_ROCK); + } EXPECT_EQ(gBattleStruct->hazardsQueue[1][4], HAZARDS_NONE); EXPECT_EQ(gBattleStruct->hazardsQueue[1][5], HAZARDS_NONE); } diff --git a/test/battle/move_effect/focus_punch.c b/test/battle/move_effect/focus_punch.c index 3f8f96d4c4..35f26ae9ee 100644 --- a/test/battle/move_effect/focus_punch.c +++ b/test/battle/move_effect/focus_punch.c @@ -118,6 +118,9 @@ AI_SINGLE_BATTLE_TEST("AI won't use status moves if the player's best attacking GIVEN { ASSUME(GetMoveEffect(MOVE_FOCUS_PUNCH) == EFFECT_FOCUS_PUNCH); ASSUME(GetMoveCategory(MOVE_SWORDS_DANCE) == DAMAGE_CATEGORY_STATUS); + // If Clefable is Normal-type, it will always use Play Rough. + ASSUME(GetSpeciesType(SPECIES_CLEFABLE, 0) == TYPE_FAIRY); + ASSUME(GetSpeciesType(SPECIES_CLEFABLE, 1) == TYPE_FAIRY); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); PLAYER(SPECIES_SNORLAX) { Moves(MOVE_FOCUS_PUNCH, MOVE_POUND); } OPPONENT(SPECIES_CLEFABLE) { Moves(MOVE_PLAY_ROUGH, MOVE_SWORDS_DANCE); } diff --git a/test/battle/move_effect/glaive_rush.c b/test/battle/move_effect/glaive_rush.c index faf1f96664..ff949a57cd 100644 --- a/test/battle/move_effect/glaive_rush.c +++ b/test/battle/move_effect/glaive_rush.c @@ -96,8 +96,8 @@ SINGLE_BATTLE_TEST("Glaive Rush doesn't affect the user if the effect is blocked { u32 species; - PARAMETRIZE { species = SPECIES_CLEFAIRY; } - PARAMETRIZE { species = SPECIES_SHELLOS; } // Closest mon in both Defense and Sp. Defense + PARAMETRIZE { species = SPECIES_FIDOUGH; } + PARAMETRIZE { species = SPECIES_MAGNEMITE; } // Closest mon in both Defense and Sp. Defense GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -105,7 +105,7 @@ SINGLE_BATTLE_TEST("Glaive Rush doesn't affect the user if the effect is blocked } WHEN { TURN { MOVE(player, MOVE_GLAIVE_RUSH); MOVE(opponent, MOVE_SCRATCH); } } SCENE { - if (species == SPECIES_CLEFAIRY) + if (species == SPECIES_FIDOUGH) NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_GLAIVE_RUSH, player); else ANIMATION(ANIM_TYPE_MOVE, MOVE_GLAIVE_RUSH, player); diff --git a/test/battle/move_effect/last_respects.c b/test/battle/move_effect/last_respects.c index 6ef4c73c2d..1e0ddaed28 100644 --- a/test/battle/move_effect/last_respects.c +++ b/test/battle/move_effect/last_respects.c @@ -13,7 +13,7 @@ SINGLE_BATTLE_TEST("Last Respects power is multiplied by the amount of fainted m PARAMETRIZE { faintCount = 1; } PARAMETRIZE { faintCount = 2; } GIVEN { - PLAYER(SPECIES_GOLEM); // Not Wobbuffet to omit type effectiveness + PLAYER(SPECIES_HITMONLEE); // Not Wobbuffet to omit type effectiveness PLAYER(SPECIES_GEODUDE); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LEPPA_BERRY); Moves(MOVE_RECYCLE, MOVE_NONE, MOVE_NONE, MOVE_NONE); } } WHEN { @@ -44,7 +44,7 @@ SINGLE_BATTLE_TEST("Last Respects power is multiplied by the amount of fainted m PARAMETRIZE { faintCount = 2; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LEPPA_BERRY); Moves(MOVE_RECYCLE, MOVE_NONE, MOVE_NONE, MOVE_NONE); } - OPPONENT(SPECIES_GOLEM); // Not Wobbuffet to omit type effectiveness + OPPONENT(SPECIES_HITMONLEE); // Not Wobbuffet to omit type effectiveness OPPONENT(SPECIES_GEODUDE); } WHEN { for (j = 0; j < faintCount; j++) diff --git a/test/battle/move_effect/protect.c b/test/battle/move_effect/protect.c index 97a09bd922..92006e8d54 100644 --- a/test/battle/move_effect/protect.c +++ b/test/battle/move_effect/protect.c @@ -398,12 +398,15 @@ DOUBLE_BATTLE_TEST("Protect: Wide Guard protects self and ally from multi-target } } -DOUBLE_BATTLE_TEST("Protect: Wide Guard can not fail on consecutive turns") +DOUBLE_BATTLE_TEST("Protect: Wide Guard can not fail on consecutive turns (Gen6+)") { - u8 turns; + u32 turns, config, passes; - PASSES_RANDOMLY(2, 2); + PARAMETRIZE { config = GEN_5; passes = 0; } + PARAMETRIZE { config = GEN_6; passes = 2; } + PASSES_RANDOMLY(passes, 2); GIVEN { + WITH_CONFIG(GEN_CONFIG_WIDE_GUARD, config); ASSUME(GetMoveTarget(MOVE_HYPER_VOICE) == MOVE_TARGET_BOTH); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); @@ -431,8 +434,8 @@ DOUBLE_BATTLE_TEST("Protect: Quick Guard protects self and ally from priority mo u16 move = MOVE_NONE; struct BattlePokemon *targetOpponent = NULL; - PARAMETRIZE { move = MOVE_SCRATCH; targetOpponent = opponentLeft; } - PARAMETRIZE { move = MOVE_SCRATCH; targetOpponent = opponentRight; } + PARAMETRIZE { move = MOVE_SCRATCH; targetOpponent = opponentLeft; } + PARAMETRIZE { move = MOVE_SCRATCH; targetOpponent = opponentRight; } PARAMETRIZE { move = MOVE_QUICK_ATTACK; targetOpponent = opponentLeft; } PARAMETRIZE { move = MOVE_QUICK_ATTACK; targetOpponent = opponentRight; } @@ -461,12 +464,15 @@ DOUBLE_BATTLE_TEST("Protect: Quick Guard protects self and ally from priority mo } } -DOUBLE_BATTLE_TEST("Protect: Quick Guard can not fail on consecutive turns") +DOUBLE_BATTLE_TEST("Protect: Quick Guard can not fail on consecutive turns (Gen6+)") { - u8 turns; + u32 turns, config, passes; - PASSES_RANDOMLY(2, 2); + PARAMETRIZE { config = GEN_5; passes = 0; } + PARAMETRIZE { config = GEN_6; passes = 2; } + PASSES_RANDOMLY(passes, 2); GIVEN { + WITH_CONFIG(GEN_CONFIG_QUICK_GUARD, config); ASSUME(GetMovePriority(MOVE_QUICK_ATTACK) == 1); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); diff --git a/test/battle/move_effect/speed_down.c b/test/battle/move_effect/speed_down.c index a3b5e26be8..0be3daa90d 100644 --- a/test/battle/move_effect/speed_down.c +++ b/test/battle/move_effect/speed_down.c @@ -10,6 +10,7 @@ DOUBLE_BATTLE_TEST("Speed Down: Cotton Spore does not fail if it is blocked by o GIVEN { ASSUME(GetMoveEffect(MOVE_COTTON_SPORE) == EFFECT_SPEED_DOWN_2); + ASSUME(GetMoveTarget(MOVE_COTTON_SPORE) == MOVE_TARGET_BOTH); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_SHELLDER) { Ability(abilityOne); } diff --git a/test/battle/move_effect/upper_hand.c b/test/battle/move_effect/upper_hand.c index 36b35781e3..facfaf4c2a 100644 --- a/test/battle/move_effect/upper_hand.c +++ b/test/battle/move_effect/upper_hand.c @@ -68,6 +68,7 @@ SINGLE_BATTLE_TEST("Upper Hand succeeds if the target's move is boosted in prior GIVEN { ASSUME(GetMoveCategory(MOVE_DRAINING_KISS) == DAMAGE_CATEGORY_SPECIAL); ASSUME(GetMovePriority(MOVE_DRAINING_KISS) == 0); + ASSUME(IsHealingMove(MOVE_DRAINING_KISS)); // Doesn't have the Healing Move flag in Gen 5 PLAYER(SPECIES_MIENSHAO) { Speed(10); } OPPONENT(SPECIES_COMFEY) { Speed(5); Ability(ABILITY_TRIAGE); } } WHEN { @@ -85,6 +86,7 @@ SINGLE_BATTLE_TEST("Upper Hand fails if the target moves first") GIVEN { ASSUME(GetMoveCategory(MOVE_DRAINING_KISS) == DAMAGE_CATEGORY_SPECIAL); ASSUME(GetMovePriority(MOVE_DRAINING_KISS) == 0); + ASSUME(IsHealingMove(MOVE_DRAINING_KISS)); // Doesn't have the Healing Move flag in Gen 5 PLAYER(SPECIES_MIENSHAO) { Speed(5); } OPPONENT(SPECIES_COMFEY) { Speed(10); Ability(ABILITY_TRIAGE); } } WHEN { diff --git a/test/battle/move_effect_secondary/dire_claw.c b/test/battle/move_effect_secondary/dire_claw.c index abd4563132..6ad8e3dce9 100644 --- a/test/battle/move_effect_secondary/dire_claw.c +++ b/test/battle/move_effect_secondary/dire_claw.c @@ -63,6 +63,7 @@ SINGLE_BATTLE_TEST("Dire Claw cannot poison/paralyze poison/electric types respe SINGLE_BATTLE_TEST("Dire Claw cannot poison/paralyze/cause to fall asleep Pokémon with abilities preventing respective statuses") { + KNOWN_FAILING; u8 statusAnim; u16 species, ability; u32 rng; @@ -75,6 +76,7 @@ SINGLE_BATTLE_TEST("Dire Claw cannot poison/paralyze/cause to fall asleep Pokém PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = MOVE_EFFECT_SLEEP; species = SPECIES_HYPNO; ability = ABILITY_INSOMNIA; } GIVEN { + WITH_CONFIG(GEN_CONFIG_PARALYZE_ELECTRIC, GEN_5); // To prevent Electric paralysis immunity from affecting the test PLAYER(SPECIES_WOBBUFFET); OPPONENT(species) { Ability(ability); } } WHEN { From 935817bf37558cf1f47c4b6478ced320ce811a7e Mon Sep 17 00:00:00 2001 From: moostoet <70690976+moostoet@users.noreply.github.com> Date: Sun, 23 Nov 2025 16:31:19 +0100 Subject: [PATCH 09/19] Fix Protosynthesis stat boosts ignoring speed drops (#8277) --- include/battle.h | 2 +- include/battle_script_commands.h | 2 + src/battle_main.c | 4 +- src/battle_script_commands.c | 19 ---- src/battle_util.c | 134 +++++++++++++++++++++-- test/battle/ability/protosynthesis.c | 92 ++++++++++++++++ test/battle/ability/quark_drive.c | 53 +++++++++ test/battle/hold_effect/booster_energy.c | 46 ++++++++ 8 files changed, 323 insertions(+), 29 deletions(-) diff --git a/include/battle.h b/include/battle.h index 6c93e91d66..864b6a1217 100755 --- a/include/battle.h +++ b/include/battle.h @@ -134,7 +134,7 @@ struct DisableStruct u8 endured:1; u8 octolockedBy:3; u8 tryEjectPack:1; - u8 padding:4; + u8 paradoxBoostedStat:4; }; // Fully Cleared each turn after end turn effects are done. A few things are cleared before end turn effects diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index 4ba801b98c..7bce90abde 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -69,6 +69,8 @@ u32 GetNaturePowerMove(u32 battler); void StealTargetItem(u8 battlerStealer, u8 battlerItem); u8 GetCatchingBattler(void); u32 GetHighestStatId(u32 battlerId); +u32 GetParadoxHighestStatId(u32 battlerId); +u32 GetParadoxBoostedStatId(u32 battlerId); bool32 ProteanTryChangeType(u32 battler, u32 ability, u32 move, u32 moveType); bool32 IsMoveNotAllowedInSkyBattles(u32 move); bool32 DoSwitchInAbilities(u32 battlerId); diff --git a/src/battle_main.c b/src/battle_main.c index c8c8480007..2b663dc49c 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4770,9 +4770,9 @@ u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, enum ItemHoldEffect h else if (ability == ABILITY_SLOW_START && gDisableStructs[battler].slowStartTimer != 0) speed /= 2; else if (ability == ABILITY_PROTOSYNTHESIS && !(gBattleMons[battler].volatiles.transformed) && ((gBattleWeather & B_WEATHER_SUN && HasWeatherEffect()) || gDisableStructs[battler].boosterEnergyActivated)) - speed = (GetHighestStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; + speed = (GetParadoxBoostedStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; else if (ability == ABILITY_QUARK_DRIVE && !(gBattleMons[battler].volatiles.transformed) && (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gDisableStructs[battler].boosterEnergyActivated)) - speed = (GetHighestStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; + speed = (GetParadoxBoostedStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; else if (ability == ABILITY_UNBURDEN && gDisableStructs[battler].unburdenActive) speed *= 2; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 319bee99e1..69c23da4d7 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -9408,25 +9408,6 @@ u32 IsAbilityStatusProtected(u32 battler, u32 ability) || IsFlowerVeilProtected(battler); } -u32 GetHighestStatId(u32 battler) -{ - u32 i, highestId = STAT_ATK, highestStat = gBattleMons[battler].attack; - - for (i = STAT_DEF; i < NUM_STATS; i++) - { - u16 *statVal = &gBattleMons[battler].attack + (i - 1); - if (*statVal > highestStat && i != STAT_SPEED) - { - highestStat = *statVal; - highestId = i; - } - } - if (gBattleMons[battler].speed > highestStat) - highestId = STAT_SPEED; - - return highestId; -} - static bool32 IsRototillerAffected(u32 battler) { if (!IsBattlerAlive(battler)) diff --git a/src/battle_util.c b/src/battle_util.c index b30c6960b9..baa77a27b9 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -68,6 +68,8 @@ static bool32 CanBeInfinitelyConfused(u32 battler); static bool32 IsNonVolatileStatusBlocked(u32 battlerDef, u32 abilityDef, u32 abilityAffected, const u8 *battleScript, enum FunctionCallOption option); static bool32 CanSleepDueToSleepClause(u32 battlerAtk, u32 battlerDef, enum FunctionCallOption option); static bool32 IsOpposingSideEmpty(u32 battler); +static void ResetParadoxWeatherStat(u32 battler); +static void ResetParadoxTerrainStat(u32 battler); ARM_FUNC NOINLINE static uq4_12_t PercentToUQ4_12(u32 percent); ARM_FUNC NOINLINE static uq4_12_t PercentToUQ4_12_Floored(u32 percent); @@ -249,7 +251,10 @@ bool32 EndOrContinueWeather(void) { gBattleWeather = B_WEATHER_NONE; for (u32 battler = 0; battler < gBattlersCount; battler++) + { gDisableStructs[battler].weatherAbilityDone = FALSE; + ResetParadoxWeatherStat(battler); + } gBattleCommunication[MULTISTRING_CHOOSER] = sBattleWeatherInfo[currBattleWeather].endMessage; BattleScriptExecute(BattleScript_WeatherFaded); return TRUE; @@ -1718,6 +1723,7 @@ void TryToRevertMimicryAndFlags(void) for (u32 battler = 0; battler < gBattlersCount; battler++) { gDisableStructs[battler].terrainAbilityDone = FALSE; + ResetParadoxTerrainStat(battler); if (IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MIMICRY)) RESTORE_BATTLER_TYPE(battler); } @@ -2810,7 +2816,10 @@ bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, u32 ability) { gBattleWeather = sBattleWeatherInfo[battleWeatherId].flag; for (u32 i = 0; i < gBattlersCount; i++) + { gDisableStructs[i].weatherAbilityDone = FALSE; + ResetParadoxWeatherStat(i); + } return TRUE; } else @@ -2824,7 +2833,10 @@ bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, u32 ability) else gWishFutureKnock.weatherDuration = 5; for (u32 i = 0; i < gBattlersCount; i++) + { gDisableStructs[i].weatherAbilityDone = FALSE; + ResetParadoxWeatherStat(i); + } return TRUE; } @@ -2841,7 +2853,10 @@ bool32 TryChangeBattleTerrain(u32 battler, u32 statusFlag) gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY; gFieldStatuses |= statusFlag; for (u32 i = 0; i < gBattlersCount; i++) + { gDisableStructs[i].terrainAbilityDone = FALSE; + ResetParadoxTerrainStat(i); + } if (GetBattlerHoldEffect(battler, TRUE) == HOLD_EFFECT_TERRAIN_EXTENDER) gFieldTimers.terrainTimer = gBattleTurnCounter + 8; else @@ -5224,7 +5239,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && !gDisableStructs[battler].boosterEnergyActivated) { gDisableStructs[battler].weatherAbilityDone = TRUE; - PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); + gDisableStructs[battler].paradoxBoostedStat = GetParadoxHighestStatId(battler); + PREPARE_STAT_BUFFER(gBattleTextBuff1, gDisableStructs[battler].paradoxBoostedStat); gBattleScripting.battler = battler; BattleScriptPushCursorAndCallback(BattleScript_ProtosynthesisActivates); effect++; @@ -5253,7 +5269,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && !gDisableStructs[battler].boosterEnergyActivated) { gDisableStructs[battler].terrainAbilityDone = TRUE; - PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); + gDisableStructs[battler].paradoxBoostedStat = GetParadoxHighestStatId(battler); + PREPARE_STAT_BUFFER(gBattleTextBuff1, gDisableStructs[battler].paradoxBoostedStat); gBattlerAbility = gBattleScripting.battler = battler; BattleScriptPushCursorAndCallback(BattleScript_QuarkDriveActivates); effect++; @@ -5546,6 +5563,108 @@ bool32 CanBeParalyzed(u32 battlerAtk, u32 battlerDef, u32 abilityDef) return FALSE; } +u32 GetHighestStatId(u32 battler) +{ + u32 highestId = STAT_ATK; + u32 highestStat = gBattleMons[battler].attack; + + for (u32 stat = STAT_DEF; stat < NUM_STATS; stat++) + { + if (stat == STAT_SPEED) + continue; + + u16 *statVal = &gBattleMons[battler].attack + (stat - 1); + if (*statVal > highestStat) + { + highestStat = *statVal; + highestId = stat; + } + } + + if (gBattleMons[battler].speed > highestStat) + highestId = STAT_SPEED; + + return highestId; +} + +static u32 GetStatValueWithStages(u32 battler, u32 stat) +{ + u32 statValue; + + switch (stat) + { + case STAT_ATK: + statValue = gBattleMons[battler].attack; + break; + case STAT_DEF: + statValue = gBattleMons[battler].defense; + break; + case STAT_SPATK: + statValue = gBattleMons[battler].spAttack; + break; + case STAT_SPDEF: + statValue = gBattleMons[battler].spDefense; + break; + case STAT_SPEED: + statValue = gBattleMons[battler].speed; + break; + default: + return 0; + } + + statValue *= gStatStageRatios[gBattleMons[battler].statStages[stat]][0]; + statValue /= gStatStageRatios[gBattleMons[battler].statStages[stat]][1]; + + return statValue; +} + +u32 GetParadoxHighestStatId(u32 battler) +{ + u32 highestId = STAT_ATK; + u32 highestStat = GetStatValueWithStages(battler, STAT_ATK); + + for (u32 stat = STAT_DEF; stat < NUM_STATS; stat++) + { + if (stat == STAT_SPEED) + continue; + + u32 statValue = GetStatValueWithStages(battler, stat); + if (statValue > highestStat) + { + highestStat = statValue; + highestId = stat; + } + } + + u32 speed = GetStatValueWithStages(battler, STAT_SPEED); + if (speed > highestStat) + highestId = STAT_SPEED; + + return highestId; +} + +static void ResetParadoxWeatherStat(u32 battler) +{ + if (gBattleMons[battler].ability == ABILITY_PROTOSYNTHESIS + && !gDisableStructs[battler].boosterEnergyActivated) + gDisableStructs[battler].paradoxBoostedStat = 0; +} + +static void ResetParadoxTerrainStat(u32 battler) +{ + if (gBattleMons[battler].ability == ABILITY_QUARK_DRIVE + && !gDisableStructs[battler].boosterEnergyActivated) + gDisableStructs[battler].paradoxBoostedStat = 0; +} + +u32 GetParadoxBoostedStatId(u32 battler) +{ + if (gDisableStructs[battler].paradoxBoostedStat == 0) + gDisableStructs[battler].paradoxBoostedStat = GetParadoxHighestStatId(battler); + + return gDisableStructs[battler].paradoxBoostedStat; +} + bool32 CanBeFrozen(u32 battlerAtk, u32 battlerDef, u32 abilityDef) { if (CanSetNonVolatileStatus( @@ -6195,7 +6314,8 @@ u32 TryBoosterEnergy(u32 battler, u32 ability, enum ItemCaseId caseID) if (((ability == ABILITY_PROTOSYNTHESIS) && !((gBattleWeather & B_WEATHER_SUN) && HasWeatherEffect())) || ((ability == ABILITY_QUARK_DRIVE) && !(gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN))) { - PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); + gDisableStructs[battler].paradoxBoostedStat = GetParadoxHighestStatId(battler); + PREPARE_STAT_BUFFER(gBattleTextBuff1, gDisableStructs[battler].paradoxBoostedStat); gBattlerAbility = gBattleScripting.battler = battler; gDisableStructs[battler].boosterEnergyActivated = TRUE; gLastUsedItem = ITEM_BOOSTER_ENERGY; @@ -8407,7 +8527,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct DamageContext *ctx) break; case ABILITY_PROTOSYNTHESIS: { - u8 defHighestStat = GetHighestStatId(battlerDef); + u32 defHighestStat = GetParadoxBoostedStatId(battlerDef); if (((ctx->weather & B_WEATHER_SUN && HasWeatherEffect()) || gDisableStructs[battlerDef].boosterEnergyActivated) && ((IsBattleMovePhysical(move) && defHighestStat == STAT_DEF) || (IsBattleMoveSpecial(move) && defHighestStat == STAT_SPDEF)) && !(gBattleMons[battlerDef].volatiles.transformed)) @@ -8416,7 +8536,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct DamageContext *ctx) break; case ABILITY_QUARK_DRIVE: { - u8 defHighestStat = GetHighestStatId(battlerDef); + u32 defHighestStat = GetParadoxBoostedStatId(battlerDef); if ((gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gDisableStructs[battlerDef].boosterEnergyActivated) && ((IsBattleMovePhysical(move) && defHighestStat == STAT_DEF) || (IsBattleMoveSpecial(move) && defHighestStat == STAT_SPDEF)) && !(gBattleMons[battlerDef].volatiles.transformed)) @@ -8685,7 +8805,7 @@ static inline u32 CalcAttackStat(struct DamageContext *ctx) case ABILITY_PROTOSYNTHESIS: if (!(gBattleMons[battlerAtk].volatiles.transformed)) { - u32 atkHighestStat = GetHighestStatId(battlerAtk); + u32 atkHighestStat = GetParadoxBoostedStatId(battlerAtk); if (((ctx->weather & B_WEATHER_SUN) && HasWeatherEffect()) || gDisableStructs[battlerAtk].boosterEnergyActivated) { if ((IsBattleMovePhysical(move) && atkHighestStat == STAT_ATK) || (IsBattleMoveSpecial(move) && atkHighestStat == STAT_SPATK)) @@ -8696,7 +8816,7 @@ static inline u32 CalcAttackStat(struct DamageContext *ctx) case ABILITY_QUARK_DRIVE: if (!(gBattleMons[battlerAtk].volatiles.transformed)) { - u32 atkHighestStat = GetHighestStatId(battlerAtk); + u32 atkHighestStat = GetParadoxBoostedStatId(battlerAtk); if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gDisableStructs[battlerAtk].boosterEnergyActivated) { if ((IsBattleMovePhysical(move) && atkHighestStat == STAT_ATK) || (IsBattleMoveSpecial(move) && atkHighestStat == STAT_SPATK)) diff --git a/test/battle/ability/protosynthesis.c b/test/battle/ability/protosynthesis.c index bad29b4c9a..d455b23911 100644 --- a/test/battle/ability/protosynthesis.c +++ b/test/battle/ability/protosynthesis.c @@ -218,3 +218,95 @@ SINGLE_BATTLE_TEST("Protosynthesis activates after weather was reset") ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); } } + +SINGLE_BATTLE_TEST("Protosynthesis accounts for Sticky Web when determining the boosted stat") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + PLAYER(SPECIES_FLUTTER_MANE) { Ability(ABILITY_PROTOSYNTHESIS); Attack(50); Defense(50); SpAttack(150); SpDefense(140); Speed(180); } + OPPONENT(SPECIES_GALVANTULA) { Speed(60); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + } WHEN { + TURN { MOVE(opponent, MOVE_STICKY_WEB); MOVE(player, MOVE_SUNNY_DAY); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_SPLASH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Flutter Mane's Protosynthesis!"); + MESSAGE("Flutter Mane's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis keeps its initial boosted stat after Speed is lowered") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_FLUTTER_MANE) { Ability(ABILITY_PROTOSYNTHESIS); Attack(10); Defense(10); SpAttack(150); SpDefense(120); Speed(180); Moves(MOVE_ROUND); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_DROUGHT); Moves(MOVE_ICY_WIND, MOVE_CELEBRATE); Speed(100); } + } WHEN { + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_ICY_WIND); } + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICY_WIND, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis recalculates the boosted stat after Neutralizing Gas leaves the field") +{ + GIVEN { + PLAYER(SPECIES_FLUTTER_MANE) { Ability(ABILITY_PROTOSYNTHESIS); Attack(10); Defense(10); SpAttack(150); SpDefense(120); Speed(180); } + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); Moves(MOVE_ICY_WIND); Speed(70); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_ICY_WIND); } + TURN { SWITCH(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_NEUTRALIZING_GAS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + NOT ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICY_WIND, opponent); + MESSAGE("The effects of the neutralizing gas wore off!"); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Flutter Mane's Protosynthesis!"); + MESSAGE("Flutter Mane's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis retains its boosted stat after Neutralizing Gas briefly suppresses it") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_FLUTTER_MANE) { Ability(ABILITY_PROTOSYNTHESIS); Attack(10); Defense(10); SpAttack(150); SpDefense(120); Speed(180); Moves(MOVE_SUNNY_DAY, MOVE_ROUND, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); Speed(1); } + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); Moves(MOVE_CELEBRATE); Speed(70); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); SWITCH(opponent, 1); } + TURN { MOVE(player, MOVE_CELEBRATE); SWITCH(opponent, 0); } + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ABILITY_POPUP(opponent, ABILITY_NEUTRALIZING_GAS); + MESSAGE("Neutralizing gas filled the area!"); + MESSAGE("The effects of the neutralizing gas wore off!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} diff --git a/test/battle/ability/quark_drive.c b/test/battle/ability/quark_drive.c index a9f6e65a4b..3523f7dca4 100644 --- a/test/battle/ability/quark_drive.c +++ b/test/battle/ability/quark_drive.c @@ -54,6 +54,59 @@ SINGLE_BATTLE_TEST("Quark Drive boosts either Attack or Special Attack, not both } } +SINGLE_BATTLE_TEST("Quark Drive keeps its initial boosted stat after Speed is lowered") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Ability(ABILITY_QUARK_DRIVE); Attack(10); Defense(10); SpAttack(150); SpDefense(120); Speed(180); Moves(MOVE_ROUND, MOVE_CELEBRATE); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); Moves(MOVE_CELEBRATE, MOVE_ICY_WIND); Speed(100); } + } WHEN { + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_ICY_WIND); } + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICY_WIND, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} + +SINGLE_BATTLE_TEST("Quark Drive retains its boosted stat after Neutralizing Gas briefly suppresses it") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Ability(ABILITY_QUARK_DRIVE); Attack(10); Defense(10); SpAttack(150); SpDefense(120); Speed(180); Moves(MOVE_ELECTRIC_TERRAIN, MOVE_ROUND, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); Speed(1); } + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); Moves(MOVE_CELEBRATE); Speed(70); } + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); SWITCH(opponent, 1); } + TURN { MOVE(player, MOVE_CELEBRATE); SWITCH(opponent, 0); } + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ABILITY_POPUP(opponent, ABILITY_NEUTRALIZING_GAS); + MESSAGE("Neutralizing gas filled the area!"); + MESSAGE("The effects of the neutralizing gas wore off!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} + SINGLE_BATTLE_TEST("Quark Drive ability pop up activates only once during the duration of electric terrain") { u16 turns; diff --git a/test/battle/hold_effect/booster_energy.c b/test/battle/hold_effect/booster_energy.c index a5113ef245..696dbbb579 100644 --- a/test/battle/hold_effect/booster_energy.c +++ b/test/battle/hold_effect/booster_energy.c @@ -65,6 +65,29 @@ SINGLE_BATTLE_TEST("Booster Energy will activate Protosynthesis after harsh sunl } } +SINGLE_BATTLE_TEST("Booster Energy's Protosynthesis boost is preserved when weather changes") +{ + GIVEN { + PLAYER(SPECIES_RAGING_BOLT) { Attack(110); Defense(100); Speed(100); SpAttack(100); SpDefense(100); Ability(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Moves(MOVE_SUNNY_DAY, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + TURN { } + TURN { } + TURN { } + TURN { } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Raging Bolt used its Booster Energy to activate Protosynthesis!"); + MESSAGE("Raging Bolt's Attack was heightened!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + MESSAGE("The sunlight faded."); + } THEN { + EXPECT(gDisableStructs[B_POSITION_PLAYER_LEFT].paradoxBoostedStat == STAT_ATK); + } +} + SINGLE_BATTLE_TEST("Booster Energy activates Protosynthesis and increases highest stat") { u32 attack, defense, speed, spAttack, spDefense; @@ -131,6 +154,29 @@ SINGLE_BATTLE_TEST("Booster Energy activates Quark Drive and increases highest s } } +SINGLE_BATTLE_TEST("Booster Energy's Quark Drive boost is preserved when terrain changes") +{ + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Attack(110); Defense(100); Speed(100); SpAttack(100); SpDefense(100); Ability(ABILITY_QUARK_DRIVE); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Moves(MOVE_GRASSY_TERRAIN, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_GRASSY_TERRAIN); } + TURN { } + TURN { } + TURN { } + TURN { } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Iron Moth used its Booster Energy to activate Quark Drive!"); + MESSAGE("Iron Moth's Attack was heightened!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASSY_TERRAIN, opponent); + MESSAGE("The grass disappeared from the battlefield."); + } THEN { + EXPECT(gDisableStructs[B_POSITION_PLAYER_LEFT].paradoxBoostedStat == STAT_ATK); + } +} + SINGLE_BATTLE_TEST("Booster Energy increases special attack by 30% if it is the highest stat", s16 damage) { u32 species; From 7fd0745766425a16c1b8d70791a3c9ac3080a475 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sun, 23 Nov 2025 17:04:01 +0100 Subject: [PATCH 10/19] Fix switch-in abilities not triggering on revive (#8293) --- data/battle_scripts_2.s | 4 + src/battle_script_commands.c | 12 +-- src/battle_util.c | 4 + test/battle/ability/unnerve.c | 25 +++++++ test/battle/item_effect/revive.c | 124 +++++++++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 6 deletions(-) diff --git a/data/battle_scripts_2.s b/data/battle_scripts_2.s index 586a79c5ef..b086582250 100755 --- a/data/battle_scripts_2.s +++ b/data/battle_scripts_2.s @@ -75,6 +75,10 @@ BattleScript_ItemRestoreHP_Party:: return BattleScript_ItemRestoreHP_SendOutRevivedBattler: + switchhandleorder BS_SCRIPTING, 0 + getswitchedmondata BS_SCRIPTING + switchindataupdate BS_SCRIPTING + trytoclearprimalweather switchinanim BS_SCRIPTING, FALSE, FALSE waitstate switchineffects BS_SCRIPTING diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 69c23da4d7..dcc03aa74e 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -4862,8 +4862,6 @@ static void Cmd_getexp(void) // not sure why gf clears the item and ability here gBattleStruct->expOrderId = 0; gBattleStruct->teamGotExpMsgPrinted = FALSE; - gBattleMons[gBattlerFainted].item = ITEM_NONE; - gBattleMons[gBattlerFainted].ability = ABILITY_NONE; gBattlescriptCurrInstr = cmd->nextInstr; } break; @@ -15192,12 +15190,14 @@ void BS_ItemRestoreHP(void) hp += healAmount; SetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_HP, &hp); - // Revived battlers on the field need to be brought back. - if (IsDoubleBattle() && battler != MAX_BATTLERS_COUNT) + u32 partner = BATTLE_PARTNER(gBattlerAttacker); + // Absent battlers on the field need to be replaced + if (IsDoubleBattle() && (gAbsentBattlerFlags & (1u << partner))) { - gAbsentBattlerFlags &= ~(1u << battler); - gBattleMons[battler].hp = hp; + gAbsentBattlerFlags &= ~(1u << partner); gBattleCommunication[MULTIUSE_STATE] = TRUE; + gBattleScripting.battler = partner; + BtlController_EmitChosenMonReturnValue(partner, B_COMM_TO_ENGINE, gBattleStruct->itemPartyIndex[gBattlerAttacker], NULL); } gBattlescriptCurrInstr = cmd->nextInstr; } diff --git a/src/battle_util.c b/src/battle_util.c index baa77a27b9..ecf836d882 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5195,6 +5195,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITYEFFECT_ON_WEATHER: // For ability effects that activate when the battle weather changes. + if (!IsBattlerAlive(battler)) + return effect; gLastUsedAbility = GetBattlerAbility(battler); switch (gLastUsedAbility) { @@ -5249,6 +5251,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITYEFFECT_ON_TERRAIN: // For ability effects that activate when the field terrain changes. + if (!IsBattlerAlive(battler)) + return effect; gLastUsedAbility = GetBattlerAbility(battler); switch (gLastUsedAbility) { diff --git a/test/battle/ability/unnerve.c b/test/battle/ability/unnerve.c index 9a8972eb5c..e53b510fe6 100644 --- a/test/battle/ability/unnerve.c +++ b/test/battle/ability/unnerve.c @@ -99,3 +99,28 @@ SINGLE_BATTLE_TEST("Unnerve activates only once per switch-in") } } + +DOUBLE_BATTLE_TEST("Unnerve stops applying on death but applies on revive") +{ + u16 mon; + u16 ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE;} + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + ASSUME(gItemsInfo[ITEM_RAWST_BERRY].holdEffect == HOLD_EFFECT_CURE_BRN); + ASSUME(gItemsInfo[ITEM_REVIVE].battleUsage == EFFECT_ITEM_REVIVE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(mon) { Ability(ability); HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RAWST_BERRY); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WYNAUT) { Item(ITEM_RAWST_BERRY); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); } + TURN { USE_ITEM(playerLeft, ITEM_REVIVE, partyIndex: 1); SKIP_TURN(playerRight); MOVE(opponentLeft, MOVE_WILL_O_WISP, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponentRight); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + } + +} diff --git a/test/battle/item_effect/revive.c b/test/battle/item_effect/revive.c index 9ff693fc30..df866edf0e 100644 --- a/test/battle/item_effect/revive.c +++ b/test/battle/item_effect/revive.c @@ -99,4 +99,128 @@ DOUBLE_BATTLE_TEST("Revive works for a partner in a double battle") } } +DOUBLE_BATTLE_TEST("Revive can trigger switch-in abilities") +{ + GIVEN { + PLAYER(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); } + TURN { USE_ITEM(playerRight, ITEM_REVIVE, partyIndex: 0); SKIP_TURN(playerLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + } +} + +DOUBLE_BATTLE_TEST("Revive does reset abilities") +{ + GIVEN { + PLAYER(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentRight, MOVE_WORRY_SEED, target: playerLeft); MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); } + TURN { USE_ITEM(playerRight, ITEM_REVIVE, partyIndex: 0); SKIP_TURN(playerLeft); MOVE(opponentRight, MOVE_SPORE, target: playerLeft);} + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + } +} + +DOUBLE_BATTLE_TEST("Revive does not grant a mon its pre-death status condition") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { Status1(STATUS1_SLEEP); HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); } + TURN { USE_ITEM(playerLeft, ITEM_REVIVE, partyIndex: 1); SKIP_TURN(playerRight); } + } THEN { + EXPECT_EQ(opponentRight->status1, 0); + } +} + +DOUBLE_BATTLE_TEST("Revive does not grant a mon its pre-death stat change") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerRight, MOVE_SWORDS_DANCE); MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); } + TURN { USE_ITEM(playerLeft, ITEM_REVIVE, partyIndex: 1); SKIP_TURN(playerRight); } + } THEN { + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Revive does not grant a mon its pre-death types") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_POOCHYENA); + } WHEN { + TURN { MOVE(playerRight, MOVE_REFLECT_TYPE, target: opponentRight); MOVE(opponentLeft, MOVE_PSYSHOCK, target: playerRight); MOVE(opponentRight, MOVE_SCRATCH, target: playerRight); } + TURN { USE_ITEM(playerLeft, ITEM_MAX_REVIVE, partyIndex: 1); SKIP_TURN(playerRight); MOVE(opponentLeft, MOVE_PSYCHIC, target: playerRight);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT_TYPE, playerRight); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYSHOCK, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHIC, opponentLeft); + } THEN { + EXPECT_LT(playerRight->hp, playerRight->maxHP); + } +} + +DOUBLE_BATTLE_TEST("Revive force revived pokemon to replace absent battler immediately", s16 damage) +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_INTIMIDATE; } + PARAMETRIZE { ability = ABILITY_SHED_SKIN; } + + GIVEN { + PLAYER(SPECIES_WYNAUT) { HP(1); } + PLAYER(SPECIES_WOBBUFFET) { }; + PLAYER(SPECIES_ARBOK) { Ability(ability); HP(0) ;} ; + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft);} + TURN { USE_ITEM(playerRight, ITEM_REVIVE, partyIndex: 2); SKIP_TURN(playerLeft); MOVE(opponentRight, MOVE_SCRATCH, target: playerRight); } + } SCENE { + if (ability == ABILITY_INTIMIDATE) + { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + HP_BAR(playerRight, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + TO_DO_BATTLE_TEST("Revive won't restore a battler's HP if it hasn't fainted") From 10ef7f7839f58f303ab9863a7ca48cb5043b83b6 Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Sun, 23 Nov 2025 16:29:50 +0000 Subject: [PATCH 11/19] More Neutralizing Gas cleanup (#8335) --- data/battle_scripts_1.s | 6 ------ include/battle.h | 2 +- include/battle_scripts.h | 1 - src/battle_script_commands.c | 14 +++++--------- src/battle_util.c | 4 ++-- 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 8a93eb9436..93e928c880 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -8112,12 +8112,6 @@ BattleScript_SwitchInAbilityMsg:: waitmessage B_WAIT_TIME_LONG end3 -BattleScript_SwitchInAbilityMsgRet:: - call BattleScript_AbilityPopUp - printfromtable gSwitchInAbilityStringIds - waitmessage B_WAIT_TIME_LONG - return - BattleScript_ActivateAsOne:: call BattleScript_AbilityPopUp printfromtable gSwitchInAbilityStringIds diff --git a/include/battle.h b/include/battle.h index 864b6a1217..fb9fcbc695 100755 --- a/include/battle.h +++ b/include/battle.h @@ -197,8 +197,8 @@ struct SpecialStatus u8 switchInItemDone:1; u8 instructedChosenTarget:3; u8 berryReduced:1; - u8 announceNeutralizingGas:1; // See Cmd_switchineffects u8 neutralizingGasRemoved:1; // See VARIOUS_TRY_END_NEUTRALIZING_GAS + u8 padding:1; // End of byte u8 gemParam; // End of byte diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 16a07e971a..b5d9d25109 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -287,7 +287,6 @@ extern const u8 BattleScript_AttackerAbilityStatRaiseEnd3[]; extern const u8 BattleScript_PoisonHealActivates[]; extern const u8 BattleScript_BadDreamsActivates[]; extern const u8 BattleScript_SwitchInAbilityMsg[]; -extern const u8 BattleScript_SwitchInAbilityMsgRet[]; extern const u8 BattleScript_ToxicSpikesPoisoned[]; extern const u8 BattleScript_ToxicSpikesBadlyPoisoned[]; extern const u8 BattleScript_ToxicSpikesAbsorbed[]; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index dcc03aa74e..e5d9d0a588 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -7873,13 +7873,9 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) u32 i = 0; u32 side = GetBattlerSide(battler); // Neutralizing Gas announces itself before hazards - if (gBattleMons[battler].ability == ABILITY_NEUTRALIZING_GAS && gSpecialStatuses[battler].announceNeutralizingGas == 0) + if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, battler, 0, 0, 0)) { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_NEUTRALIZING_GAS; - gSpecialStatuses[battler].announceNeutralizingGas = TRUE; - gDisableStructs[battler].neutralizingGas = TRUE; - gBattlerAbility = battler; - BattleScriptCall(BattleScript_SwitchInAbilityMsgRet); + return TRUE; } // Healing Wish activates before hazards. // Starting from Gen8 - it heals only pokemon which can be healed. In gens 5,6,7 the effect activates anyways. @@ -13103,7 +13099,7 @@ static void Cmd_setgastroacid(void) } else { - if (gBattleMons[gBattlerTarget].ability == ABILITY_NEUTRALIZING_GAS) + if (gDisableStructs[gBattlerTarget].neutralizingGas) gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = TRUE; gBattleMons[gBattlerTarget].volatiles.gastroAcid = TRUE; @@ -14681,7 +14677,7 @@ static void Cmd_tryworryseed(void) } else { - if (gBattleMons[gBattlerTarget].ability == ABILITY_NEUTRALIZING_GAS) + if (gDisableStructs[gBattlerTarget].neutralizingGas) gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = TRUE; gBattleScripting.abilityPopupOverwrite = gBattleMons[gBattlerTarget].ability; @@ -17522,7 +17518,7 @@ void BS_SetSimpleBeam(void) } else { - if (gBattleMons[gBattlerTarget].ability == ABILITY_NEUTRALIZING_GAS) + if (gDisableStructs[gBattlerTarget].neutralizingGas) gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = TRUE; gBattleScripting.abilityPopupOverwrite = gBattleMons[gBattlerTarget].ability; diff --git a/src/battle_util.c b/src/battle_util.c index ecf836d882..9c569cd989 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5185,7 +5185,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITYEFFECT_NEUTRALIZINGGAS: // Prints message only. separate from ABILITYEFFECT_ON_SWITCHIN bc activates before entry hazards - if (gBattleMons[battler].ability == ABILITY_NEUTRALIZING_GAS && !gDisableStructs[battler].neutralizingGas) + if (gLastUsedAbility == ABILITY_NEUTRALIZING_GAS && !gDisableStructs[battler].neutralizingGas) { gDisableStructs[battler].neutralizingGas = TRUE; gBattlerAbility = battler; @@ -5386,7 +5386,7 @@ u32 GetBattlerAbilityInternal(u32 battler, u32 ignoreMoldBreaker, u32 noAbilityS if (!hasAbilityShield && IsNeutralizingGasOnField() - && !gDisableStructs[battler].neutralizingGas) + && (gBattleMons[battler].ability != ABILITY_NEUTRALIZING_GAS || gBattleMons[battler].volatiles.gastroAcid)) return ABILITY_NONE; if (CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability, hasAbilityShield, ignoreMoldBreaker)) From a38e406ca4004aedf76711f014e01aadeef1dd0a Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sun, 23 Nov 2025 19:45:42 +0100 Subject: [PATCH 12/19] Prevent moves to be changed when choosing half party (#8336) --- src/party_menu.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/party_menu.c b/src/party_menu.c index 40815c9044..34d2e6c677 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -3010,6 +3010,10 @@ static void CB2_ShowPokemonSummaryScreen(void) UpdatePartyToBattleOrder(); ShowPokemonSummaryScreen(SUMMARY_MODE_LOCK_MOVES, gPlayerParty, gPartyMenu.slotId, gPlayerPartyCount - 1, CB2_ReturnToPartyMenuFromSummaryScreen); } + else if (gPartyMenu.menuType == PARTY_MENU_TYPE_CHOOSE_HALF) + { + ShowPokemonSummaryScreen(SUMMARY_MODE_LOCK_MOVES, gPlayerParty, gPartyMenu.slotId, gPlayerPartyCount - 1, CB2_ReturnToPartyMenuFromSummaryScreen); + } else { ShowPokemonSummaryScreen(SUMMARY_MODE_NORMAL, gPlayerParty, gPartyMenu.slotId, gPlayerPartyCount - 1, CB2_ReturnToPartyMenuFromSummaryScreen); From 1ea0500b955c385010b3b728f61e10893f28872b Mon Sep 17 00:00:00 2001 From: psf <77138753+pkmnsnfrn@users.noreply.github.com> Date: Sun, 23 Nov 2025 22:55:30 -0800 Subject: [PATCH 13/19] Fixed broken friendship from items in battle test and added new test for opposite case (#7872) --- include/constants/item_effects.h | 3 ++ include/pokemon.h | 2 ++ src/battle_controllers.c | 19 +--------- src/pokemon.c | 48 ++++++++++++------------- test/battle/item_effect/increase_stat.c | 28 +++++++++++++-- 5 files changed, 56 insertions(+), 44 deletions(-) diff --git a/include/constants/item_effects.h b/include/constants/item_effects.h index dbe322beac..037f28c4bc 100644 --- a/include/constants/item_effects.h +++ b/include/constants/item_effects.h @@ -93,6 +93,9 @@ #define ITEM_EFFECT_HEAL_PP 21 #define ITEM_EFFECT_NONE 22 +#define ITEM_FRIENDSHIP_MAPSEC_BONUS 1 // The amount of bonus friendship gained when an item is used on a Pokémon whose met location matches the current map section. +#define ITEM_FRIENDSHIP_LUXURY_BONUS 1 // The amount of bonus friendship gained when a Pokémon is in the Luxury Ball. + // Since X item stat increases are now handled by battle scripts, the friendship increase effect is now handled by the battle controller in HandleAction_UseItem. #define X_ITEM_FRIENDSHIP_INCREASE 1 // The amount of friendship gained by using an X item on a Pokémon in battle. #define X_ITEM_MAX_FRIENDSHIP 200 // Friendship threshold at which Pokémon stop receiving a friendship increase from using X items on them in battle. diff --git a/include/pokemon.h b/include/pokemon.h index 8852ba3c97..bee162b4f7 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -6,6 +6,7 @@ #include "constants/battle.h" #include "constants/cries.h" #include "constants/form_change_types.h" +#include "constants/hold_effects.h" #include "constants/items.h" #include "constants/map_groups.h" #include "constants/regions.h" @@ -825,6 +826,7 @@ s32 GetBattlerMultiplayerId(u16 id); u8 GetTrainerEncounterMusicId(u16 trainerOpponentId); u16 ModifyStatByNature(u8 nature, u16 stat, u8 statIndex); void AdjustFriendship(struct Pokemon *mon, u8 event); +u8 CalculateFriendshipBonuses(struct Pokemon *mon, u32 modifier, enum ItemHoldEffect itemHoldEffect); void MonGainEVs(struct Pokemon *mon, u16 defeatedSpecies); u16 GetMonEVCount(struct Pokemon *mon); void RandomlyGivePartyPokerus(struct Pokemon *party); diff --git a/src/battle_controllers.c b/src/battle_controllers.c index 79828d065d..3b416afe5b 100644 --- a/src/battle_controllers.c +++ b/src/battle_controllers.c @@ -3106,24 +3106,7 @@ void UpdateFriendshipFromXItem(u32 battler) if (friendship < X_ITEM_MAX_FRIENDSHIP) { - if (GetItemHoldEffect(heldItem) == HOLD_EFFECT_FRIENDSHIP_UP) - friendship += 150 * X_ITEM_FRIENDSHIP_INCREASE / 100; - else - friendship += X_ITEM_FRIENDSHIP_INCREASE; - - u8 pokeball; - gBattleResources->bufferA[battler][1] = REQUEST_POKEBALL_BATTLE; - GetBattlerMonData(battler, party, gBattlerPartyIndexes[battler], &pokeball); - - if (pokeball == BALL_LUXURY) - friendship++; - - u8 metLocation; - gBattleResources->bufferA[battler][1] = REQUEST_MET_LOCATION_BATTLE; - GetBattlerMonData(battler, party, gBattlerPartyIndexes[battler], &metLocation); - - if (metLocation == GetCurrentRegionMapSectionId()) - friendship++; + friendship += CalculateFriendshipBonuses(GetBattlerMon(battler), X_ITEM_FRIENDSHIP_INCREASE, GetItemHoldEffect(heldItem)); if (friendship > MAX_FRIENDSHIP) friendship = MAX_FRIENDSHIP; diff --git a/src/pokemon.c b/src/pokemon.c index 2e9d766185..a2aad772df 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -3741,17 +3741,7 @@ bool8 ExecuteTableBasedItemEffect(struct Pokemon *mon, u16 item, u8 partyIndex, { \ friendshipChange = itemEffect[itemEffectParam]; \ friendship = GetMonData(mon, MON_DATA_FRIENDSHIP, NULL); \ - if (friendshipChange > 0 && holdEffect == HOLD_EFFECT_FRIENDSHIP_UP) \ - friendship += 150 * friendshipChange / 100; \ - else \ - friendship += friendshipChange; \ - if (friendshipChange > 0) \ - { \ - if (GetMonData(mon, MON_DATA_POKEBALL, NULL) == ITEM_LUXURY_BALL) \ - friendship++; \ - if (GetMonData(mon, MON_DATA_MET_LOCATION, NULL) == GetCurrentRegionMapSectionId()) \ - friendship++; \ - } \ + friendship += CalculateFriendshipBonuses(mon,friendshipChange,holdEffect); \ if (friendship < 0) \ friendship = 0; \ if (friendship > MAX_FRIENDSHIP) \ @@ -5279,7 +5269,7 @@ void AdjustFriendship(struct Pokemon *mon, u8 event) if (species && species != SPECIES_EGG) { u8 friendshipLevel = 0; - s16 friendship = GetMonData(mon, MON_DATA_FRIENDSHIP, 0); + s32 friendship = GetMonData(mon, MON_DATA_FRIENDSHIP, 0); enum TrainerClassID opponentTrainerClass = GetTrainerClassFromId(TRAINER_BATTLE_PARAM.opponentA); if (friendship > 99) @@ -5305,18 +5295,7 @@ void AdjustFriendship(struct Pokemon *mon, u8 event) } mod = sFriendshipEventModifiers[event][friendshipLevel]; - if (mod > 0 && holdEffect == HOLD_EFFECT_FRIENDSHIP_UP) - // 50% increase, rounding down - mod = (150 * mod) / 100; - - friendship += mod; - if (mod > 0) - { - if (GetMonData(mon, MON_DATA_POKEBALL, NULL) == ITEM_LUXURY_BALL) - friendship++; - if (GetMonData(mon, MON_DATA_MET_LOCATION, NULL) == GetCurrentRegionMapSectionId()) - friendship++; - } + friendship += CalculateFriendshipBonuses(mon,mod,holdEffect); if (friendship < 0) friendship = 0; @@ -5327,6 +5306,27 @@ void AdjustFriendship(struct Pokemon *mon, u8 event) } } +u8 CalculateFriendshipBonuses(struct Pokemon *mon, u32 modifier, enum ItemHoldEffect itemHoldEffect) +{ + u32 bonus = 0; + + if ((modifier > 0) && (itemHoldEffect == HOLD_EFFECT_FRIENDSHIP_UP)) + bonus += 150 * modifier / 100; + else + bonus += modifier; + + if (modifier == 0) + return bonus; + + if (GetMonData(mon, MON_DATA_POKEBALL, NULL) == ITEM_LUXURY_BALL) + bonus += ITEM_FRIENDSHIP_LUXURY_BONUS; + + if (GetMonData(mon, MON_DATA_MET_LOCATION, NULL) == GetCurrentRegionMapSectionId()) + bonus += ITEM_FRIENDSHIP_MAPSEC_BONUS; + + return bonus; +} + void MonGainEVs(struct Pokemon *mon, u16 defeatedSpecies) { u8 evs[NUM_STATS]; diff --git a/test/battle/item_effect/increase_stat.c b/test/battle/item_effect/increase_stat.c index b56f1a2b84..227a638c6a 100644 --- a/test/battle/item_effect/increase_stat.c +++ b/test/battle/item_effect/increase_stat.c @@ -1,4 +1,5 @@ #include "global.h" +#include "overworld.h" #include "test/battle.h" #include "constants/item_effects.h" @@ -262,12 +263,13 @@ SINGLE_BATTLE_TEST("Max Mushrooms raises battler's Speed stat", s16 damage) SINGLE_BATTLE_TEST("Using X items in battle raises Friendship", s16 damage) { u32 startingFriendship; - u8 metLocation = MAPSEC_NONE; + u8 metLocation = GetCurrentRegionMapSectionId() + 1; + PARAMETRIZE { startingFriendship = 0; } PARAMETRIZE { startingFriendship = X_ITEM_MAX_FRIENDSHIP; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { Friendship(startingFriendship); }; - // Set met location to MAPSEC_NONE to avoid getting the friendship boost + // Set met location to currentMapSec + 1 to avoid getting the friendship boost // from being met in the current map section SetMonData(&PLAYER_PARTY[0], MON_DATA_MET_LOCATION, &metLocation); OPPONENT(SPECIES_WOBBUFFET); @@ -280,3 +282,25 @@ SINGLE_BATTLE_TEST("Using X items in battle raises Friendship", s16 damage) EXPECT_EQ(player->friendship, X_ITEM_FRIENDSHIP_INCREASE); } } + +SINGLE_BATTLE_TEST("Using X items in battle where Pokemon was met raises Friendship with a bonus", s16 damage) +{ + u32 startingFriendship; + u8 metLocation = GetCurrentRegionMapSectionId(); + + PARAMETRIZE { startingFriendship = 0; } + PARAMETRIZE { startingFriendship = X_ITEM_MAX_FRIENDSHIP; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Friendship(startingFriendship); }; + // Set met location to currentMapSec to get the friendship boost + SetMonData(&PLAYER_PARTY[0], MON_DATA_MET_LOCATION, &metLocation); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { USE_ITEM(player, ITEM_X_ACCURACY); MOVE(opponent, MOVE_CELEBRATE); } + } THEN { + if (startingFriendship == X_ITEM_MAX_FRIENDSHIP) + EXPECT_EQ(player->friendship, X_ITEM_MAX_FRIENDSHIP); + else + EXPECT_EQ(player->friendship, (ITEM_FRIENDSHIP_MAPSEC_BONUS + X_ITEM_FRIENDSHIP_INCREASE)); + } +} From 68a974af8616794b3499db9bb119300ac80155ee Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Mon, 24 Nov 2025 18:34:00 +0100 Subject: [PATCH 14/19] Fix cure status item effect not working properly in doubles (#8339) --- asm/macros/battle_script.inc | 3 +- data/battle_scripts_2.s | 9 +- src/battle_script_commands.c | 32 ++++-- test/battle/item_effect/cure_status.c | 97 +++++++++++++++++++ .../battle/item_effect/heal_and_cure_status.c | 4 +- 5 files changed, 130 insertions(+), 15 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 517ab09e45..0825fe013b 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1421,9 +1421,10 @@ .4byte \restoreBattlerInstr .endm - .macro itemcurestatus jumpInstr:req + .macro itemcurestatus jumpInstr:req, restoreBattlerInstr:req callnative BS_ItemCureStatus .4byte \jumpInstr + .4byte \restoreBattlerInstr .endm .macro itemincreasestat diff --git a/data/battle_scripts_2.s b/data/battle_scripts_2.s index b086582250..2c7a56078f 100755 --- a/data/battle_scripts_2.s +++ b/data/battle_scripts_2.s @@ -87,13 +87,18 @@ BattleScript_ItemRestoreHP_SendOutRevivedBattler: BattleScript_ItemCureStatus:: call BattleScript_UseItemMessage BattleScript_ItemCureStatusAfterItemMsg: - itemcurestatus BattleScript_ItemCureStatusEnd - updatestatusicon BS_SCRIPTING + itemcurestatus BattleScript_ItemCureStatusEnd, BattleScript_CureStatus_Battler printstring STRINGID_ITEMCUREDSPECIESSTATUS waitmessage B_WAIT_TIME_LONG BattleScript_ItemCureStatusEnd: end +BattleScript_CureStatus_Battler:: + updatestatusicon BS_SCRIPTING + printstring STRINGID_ITEMCUREDSPECIESSTATUS + waitmessage B_WAIT_TIME_LONG + end + BattleScript_ItemHealAndCureStatus:: call BattleScript_UseItemMessage itemrestorehp BattleScript_ItemCureStatusAfterItemMsg, BattleScript_ItemHealAndCureStatus_Battler diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index e5d9d0a588..efe91ac3ee 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -15202,37 +15202,49 @@ void BS_ItemRestoreHP(void) void BS_ItemCureStatus(void) { - NATIVE_ARGS(const u8 *noStatusInstr); - u32 battler = gBattlerAttacker; + NATIVE_ARGS(const u8 *noStatusInstr, const u8 *restoreBattlerInstr); + u32 targetBattler = MAX_BATTLERS_COUNT; bool32 statusChanged = FALSE; struct Pokemon *party = GetBattlerParty(gBattlerAttacker); // Heal volatile conditions if battler is active. if (gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[gBattlerAttacker]) - statusChanged = ItemHealMonVolatile(battler, gLastUsedItem); + { + statusChanged = ItemHealMonVolatile(gBattlerAttacker, gLastUsedItem); + targetBattler = gBattlerAttacker; + } else if (IsDoubleBattle() && gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)]) + { statusChanged = ItemHealMonVolatile(BATTLE_PARTNER(gBattlerAttacker), gLastUsedItem); + targetBattler = BATTLE_PARTNER(gBattlerAttacker); + } // Heal Status1 conditions. - if (!HealStatusConditions(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], GetItemStatus1Mask(gLastUsedItem), battler)) + if (!HealStatusConditions(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], GetItemStatus1Mask(gLastUsedItem), targetBattler)) { statusChanged = TRUE; if (GetItemStatus1Mask(gLastUsedItem) & STATUS1_SLEEP) - gBattleMons[battler].volatiles.nightmare = FALSE; + gBattleMons[targetBattler].volatiles.nightmare = FALSE; if (ItemHasVolatileFlag(gLastUsedItem, VOLATILE_CONFUSION)) - gBattleMons[battler].volatiles.infiniteConfusion = FALSE; + gBattleMons[targetBattler].volatiles.infiniteConfusion = FALSE; } - if (statusChanged) + if (!statusChanged) + { + gBattlescriptCurrInstr = cmd->noStatusInstr; + return; + } + + PREPARE_SPECIES_BUFFER(gBattleTextBuff1, GetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_SPECIES)); + if (targetBattler == MAX_BATTLERS_COUNT) { - gBattleScripting.battler = battler; - PREPARE_SPECIES_BUFFER(gBattleTextBuff1, GetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_SPECIES)); gBattlescriptCurrInstr = cmd->nextInstr; } else { - gBattlescriptCurrInstr = cmd->noStatusInstr; + gBattleScripting.battler = targetBattler; + gBattlescriptCurrInstr = cmd->restoreBattlerInstr; } } diff --git a/test/battle/item_effect/cure_status.c b/test/battle/item_effect/cure_status.c index 919b4539af..79490b48dc 100644 --- a/test/battle/item_effect/cure_status.c +++ b/test/battle/item_effect/cure_status.c @@ -31,6 +31,29 @@ SINGLE_BATTLE_TEST("Antidote heals a battler from being poisoned") } } +DOUBLE_BATTLE_TEST("Antidote heals a battler from being poisoned (doubles)") +{ + u32 index; + struct BattlePokemon *user = NULL; + struct BattlePokemon *target = NULL; + PARAMETRIZE { index = 0; user = playerRight; target = playerLeft;} + PARAMETRIZE { index = 1; user = playerLeft; target = playerRight;} + PARAMETRIZE { index = 0; user = playerLeft; target = playerLeft;} + PARAMETRIZE { index = 1; user = playerRight; target = playerRight; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_ANTIDOTE].battleUsage == EFFECT_ITEM_CURE_STATUS); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WYNAUT) { } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { USE_ITEM(user, ITEM_ANTIDOTE, partyIndex: index); } + } THEN { + EXPECT_EQ(target->status1, STATUS1_NONE); + } +} + SINGLE_BATTLE_TEST("Antidote heals a battler from being badly poisoned") { GIVEN { @@ -135,6 +158,43 @@ SINGLE_BATTLE_TEST("Full Heal heals a battler from any primary status") } } +DOUBLE_BATTLE_TEST("Full Heal heals a battler from any primary status (doubles)") +{ + u32 statusParameters[7] = + { + STATUS1_SLEEP, + STATUS1_POISON, + STATUS1_BURN, + STATUS1_FREEZE, + STATUS1_PARALYSIS, + STATUS1_TOXIC_POISON, + STATUS1_FROSTBITE + }; + + u16 status = 0; + u32 index = 0; + struct BattlePokemon *user = NULL; + struct BattlePokemon *target = NULL; + for (u32 j = 0; j < 7; j++) + { + PARAMETRIZE {status = statusParameters[j]; user = playerRight; target = playerLeft; index = 0;} + PARAMETRIZE {status = statusParameters[j]; user = playerLeft; target = playerRight; index = 1;} + PARAMETRIZE {status = statusParameters[j]; user = playerLeft; target = playerLeft; index = 0;} + PARAMETRIZE {status = statusParameters[j]; user = playerRight; target = playerRight; index = 1;} + } + GIVEN { + ASSUME(gItemsInfo[ITEM_FULL_HEAL].battleUsage == EFFECT_ITEM_CURE_STATUS); + PLAYER(SPECIES_WOBBUFFET) { Status1(status); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { USE_ITEM(user, ITEM_FULL_HEAL, partyIndex: index); } + } THEN { + EXPECT_EQ(target->status1, STATUS1_NONE); + } +} + SINGLE_BATTLE_TEST("Heal Powder heals a battler from any primary status") { u16 status; @@ -158,6 +218,43 @@ SINGLE_BATTLE_TEST("Heal Powder heals a battler from any primary status") } } +DOUBLE_BATTLE_TEST("Heal Powder heals a battler from any primary status (doubles)") +{ + u32 statusParameters[7] = + { + STATUS1_SLEEP, + STATUS1_POISON, + STATUS1_BURN, + STATUS1_FREEZE, + STATUS1_PARALYSIS, + STATUS1_TOXIC_POISON, + STATUS1_FROSTBITE + }; + + u16 status = 0; + u32 index = 0; + struct BattlePokemon *user = NULL; + struct BattlePokemon *target = NULL; + for (u32 j = 0; j < 7; j++) + { + PARAMETRIZE {status = statusParameters[j]; user = playerRight; target = playerLeft; index = 0;} + PARAMETRIZE {status = statusParameters[j]; user = playerLeft; target = playerRight; index = 1;} + PARAMETRIZE {status = statusParameters[j]; user = playerLeft; target = playerLeft; index = 0;} + PARAMETRIZE {status = statusParameters[j]; user = playerRight; target = playerRight; index = 1;} + } + GIVEN { + ASSUME(gItemsInfo[ITEM_HEAL_POWDER].battleUsage == EFFECT_ITEM_CURE_STATUS); + PLAYER(SPECIES_WOBBUFFET) { Status1(status); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { USE_ITEM(user, ITEM_HEAL_POWDER, partyIndex: index); } + } THEN { + EXPECT_EQ(target->status1, STATUS1_NONE); + } +} + SINGLE_BATTLE_TEST("Pewter Crunchies heals a battler from any primary status") { u16 status; diff --git a/test/battle/item_effect/heal_and_cure_status.c b/test/battle/item_effect/heal_and_cure_status.c index 99507df3cd..d85f11b33d 100644 --- a/test/battle/item_effect/heal_and_cure_status.c +++ b/test/battle/item_effect/heal_and_cure_status.c @@ -43,8 +43,8 @@ SINGLE_BATTLE_TEST("Full Restore restores a party members HP and cures any prima PARAMETRIZE { status = STATUS1_SLEEP; } PARAMETRIZE { status = STATUS1_NONE; } GIVEN { - PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(300); Status1(status); } - PLAYER(SPECIES_WYNAUT) { HP(1); MaxHP(300); Status1(status); } + PLAYER(SPECIES_WOBBUFFET) { HP(100); MaxHP(300); Status1(status); } + PLAYER(SPECIES_WYNAUT) { HP(100); MaxHP(300); Status1(status); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { USE_ITEM(player, ITEM_FULL_RESTORE, partyIndex: 1); } From ec3e16801a0406c3d45f5499b7a156fae03ab25f Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Tue, 25 Nov 2025 10:09:10 +0100 Subject: [PATCH 15/19] Fix infinite confusion (berserk gene) not being cured by cure_status bag items (#8343) --- src/battle_script_commands.c | 2 -- src/battle_util.c | 6 ++-- test/battle/hold_effect/berserk_gene.c | 50 ++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index efe91ac3ee..6fc03dcf26 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -15226,8 +15226,6 @@ void BS_ItemCureStatus(void) statusChanged = TRUE; if (GetItemStatus1Mask(gLastUsedItem) & STATUS1_SLEEP) gBattleMons[targetBattler].volatiles.nightmare = FALSE; - if (ItemHasVolatileFlag(gLastUsedItem, VOLATILE_CONFUSION)) - gBattleMons[targetBattler].volatiles.infiniteConfusion = FALSE; } if (!statusChanged) diff --git a/src/battle_util.c b/src/battle_util.c index 9c569cd989..0a09da52b2 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -11748,9 +11748,10 @@ bool32 ItemHealMonVolatile(u32 battler, u16 itemId) const u8 *effect = GetItemEffect(itemId); if (effect[3] & ITEM3_STATUS_ALL) { - statusChanged = (gBattleMons[battler].volatiles.infatuation || gBattleMons[battler].volatiles.confusionTurns > 0); + statusChanged = (gBattleMons[battler].volatiles.infatuation || gBattleMons[battler].volatiles.confusionTurns > 0 || gBattleMons[battler].volatiles.infiniteConfusion); gBattleMons[battler].volatiles.infatuation = 0; gBattleMons[battler].volatiles.confusionTurns = 0; + gBattleMons[battler].volatiles.infiniteConfusion = FALSE; } else if (effect[0] & ITEM0_INFATUATION) { @@ -11759,8 +11760,9 @@ bool32 ItemHealMonVolatile(u32 battler, u16 itemId) } else if (effect[3] & ITEM3_CONFUSION) { - statusChanged = gBattleMons[battler].volatiles.confusionTurns > 0; + statusChanged = (gBattleMons[battler].volatiles.confusionTurns > 0 || gBattleMons[battler].volatiles.infiniteConfusion); gBattleMons[battler].volatiles.confusionTurns = 0; + gBattleMons[battler].volatiles.infiniteConfusion = FALSE; } return statusChanged; diff --git a/test/battle/hold_effect/berserk_gene.c b/test/battle/hold_effect/berserk_gene.c index 5331f57849..35e79bcaf4 100644 --- a/test/battle/hold_effect/berserk_gene.c +++ b/test/battle/hold_effect/berserk_gene.c @@ -252,3 +252,53 @@ SINGLE_BATTLE_TEST("Berserk Gene does not cause an infinite loop") MESSAGE("Using Berserk Gene, the Attack of the opposing Wobbuffet sharply rose!"); } } + +SINGLE_BATTLE_TEST("Berserker Gene confusion can be healed with bag items") +{ + u16 item; + PARAMETRIZE { item = ITEM_FULL_HEAL; } + PARAMETRIZE { item = ITEM_HEAL_POWDER; } + PARAMETRIZE { item = ITEM_PEWTER_CRUNCHIES; } + PARAMETRIZE { item = ITEM_LAVA_COOKIE; } + PARAMETRIZE { item = ITEM_RAGE_CANDY_BAR; } + PARAMETRIZE { item = ITEM_OLD_GATEAU; } + PARAMETRIZE { item = ITEM_CASTELIACONE; } + PARAMETRIZE { item = ITEM_LUMIOSE_GALETTE; } + PARAMETRIZE { item = ITEM_SHALOUR_SABLE; } + PARAMETRIZE { item = ITEM_BIG_MALASADA; } + PARAMETRIZE { item = ITEM_JUBILIFE_MUFFIN; } + GIVEN { + ASSUME(gItemsInfo[item].battleUsage == EFFECT_ITEM_CURE_STATUS); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_BERSERK_GENE);}; + OPPONENT(SPECIES_GENGAR); + } WHEN { + TURN { USE_ITEM(player, item, partyIndex: 0); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player); + MESSAGE("Wobbuffet had its status healed!"); + } THEN { + EXPECT(player->volatiles.infiniteConfusion == 0); + } +} + +SINGLE_BATTLE_TEST("Berserker Gene confusion can be healed with used held items") +{ + u16 item; + PARAMETRIZE { item = ITEM_PERSIM_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_PERSIM_BERRY].holdEffect == HOLD_EFFECT_CURE_CONFUSION); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_BERSERK_GENE);}; + OPPONENT(SPECIES_WOBBUFFET) { Item(item);}; + } WHEN { + TURN { MOVE(player, MOVE_COVET, WITH_RNG(RNG_CONFUSION, FALSE)); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } THEN { + EXPECT(player->volatiles.infiniteConfusion == 0); + } +} From b6c6cf9cd1bcc2ff46043cb0587fb3f2fb91234d Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Tue, 25 Nov 2025 13:02:31 +0100 Subject: [PATCH 16/19] Add tests to verify aromatherapy is not affected by heal bell config (#8344) --- test/battle/move_effect/heal_bell.c | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/battle/move_effect/heal_bell.c b/test/battle/move_effect/heal_bell.c index b3f6af0c45..4c86fd11c0 100644 --- a/test/battle/move_effect/heal_bell.c +++ b/test/battle/move_effect/heal_bell.c @@ -164,3 +164,53 @@ SINGLE_BATTLE_TEST("Heal Bell cures a Soundproof user (Gen5, Gen8+)") } } } + +DOUBLE_BATTLE_TEST("Aromatherapy cure Soundproof battlers regardless of config") +{ + u32 ability, config; + + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_4; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_5; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_6; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_8; } + + GIVEN { + ASSUME(!IsSoundMove(MOVE_AROMATHERAPY)); + WITH_CONFIG(GEN_CONFIG_HEAL_BELL_SOUNDPROOF, config); + PLAYER(SPECIES_WOBBUFFET) { Ability(ability); Status1(STATUS1_POISON); }; + PLAYER(SPECIES_EXPLOUD) { Ability(ability); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_AROMATHERAPY, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AROMATHERAPY, playerLeft); + NONE_OF { + MESSAGE("Exploud was hurt by its poisoning!"); + MESSAGE("Wobbuffet was hurt by its poisoning!"); + } + } +} + +SINGLE_BATTLE_TEST("Aromatherapy cures inactive Soundproof Pokemon regardless of config") +{ + u32 config, ability; + + PARAMETRIZE { config = GEN_4, ability = ABILITY_SOUNDPROOF; } + PARAMETRIZE { config = GEN_5, ability = ABILITY_SOUNDPROOF; } + + GIVEN { + ASSUME(!IsSoundMove(MOVE_AROMATHERAPY)); + WITH_CONFIG(GEN_CONFIG_HEAL_BELL_SOUNDPROOF, config); + PLAYER(SPECIES_WOBBUFFET) { } + PLAYER(SPECIES_EXPLOUD) { Ability(ability); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_AROMATHERAPY, target: player); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AROMATHERAPY, player); + SEND_IN_MESSAGE("Exploud"); + NOT MESSAGE("Exploud was hurt by its poisoning!"); + } +} From 79163deb248e71285b3714d1a388d73e2f51d81f Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Tue, 25 Nov 2025 13:04:19 +0100 Subject: [PATCH 17/19] Pre gen 5 encored move now signals the test engine a move is happening (#8338) --- src/battle_main.c | 2 ++ test/battle/ai/ai_switching.c | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/battle_main.c b/src/battle_main.c index 2b663dc49c..2b4b0aafd1 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4273,6 +4273,8 @@ static void HandleTurnActionSelectionState(void) gChosenMoveByBattler[battler] = gDisableStructs[battler].encoredMove; gBattleStruct->chosenMovePositions[battler] = gDisableStructs[battler].encoredMovePos; gBattleCommunication[battler] = STATE_WAIT_ACTION_CONFIRMED_STANDBY; + if (gTestRunnerEnabled) + TestRunner_Battle_CheckChosenMove(battler, gDisableStructs[battler].encoredMove, gDisableStructs[battler].encoredMovePos); return; } else diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 08262d8755..329add5a07 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -953,6 +953,22 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will stay in if Encore'd into } AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if Encore'd into neutral move with good switchin 50% of the time") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_ENCORE_DAMAGE_PERCENTAGE, 100, RNG_AI_SWITCH_ENCORE); + GIVEN { + WITH_CONFIG(GEN_CONFIG_ENCORE_TARGET, GEN_3); + ASSUME(GetMoveEffect(MOVE_ENCORE) == EFFECT_ENCORE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_AZURILL) { Moves(MOVE_SCRATCH, MOVE_ENCORE); } + OPPONENT(SPECIES_ODDISH) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ARON) { Moves(MOVE_METAL_CLAW); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_ENCORE); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if Encore'd into neutral move with good switchin 50% of the time (Gen 5+)") { PASSES_RANDOMLY(SHOULD_SWITCH_ENCORE_DAMAGE_PERCENTAGE, 100, RNG_AI_SWITCH_ENCORE); GIVEN { From 152ad884365104d331f96aa3b570d7ce78b1c46f Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Tue, 25 Nov 2025 14:17:21 -0300 Subject: [PATCH 18/19] Fix `B_PHYSICAL_SPECIAL_SPLIT` when set to Gen 4 (#8348) --- src/battle_util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_util.c b/src/battle_util.c index 0a09da52b2..ae522ad593 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -10650,7 +10650,7 @@ enum DamageCategory GetBattleMoveCategory(u32 move) return DAMAGE_CATEGORY_STATUS; } - if (B_PHYSICAL_SPECIAL_SPLIT <= GEN_4) + if (B_PHYSICAL_SPECIAL_SPLIT < GEN_4) return gTypesInfo[GetBattleMoveType(move)].damageCategory; return GetMoveCategory(move); From fbc640d6922a2ddc30131e7eba4f467ada7a814c Mon Sep 17 00:00:00 2001 From: moostoet <70690976+moostoet@users.noreply.github.com> Date: Tue, 25 Nov 2025 22:18:51 +0100 Subject: [PATCH 19/19] Refactor Beat Up handling for Gen 3/4 defaults, fix crit check, and expand test coverage (#8307) --- asm/macros/battle_script.inc | 4 +- data/battle_scripts_1.s | 31 +- include/battle.h | 2 +- include/constants/battle_script_commands.h | 7 +- include/constants/generational_changes.h | 1 + include/generational_changes.h | 1 + src/battle_ai_util.c | 2 +- src/battle_script_commands.c | 49 +-- src/battle_util.c | 40 ++- src/data/battle_move_effects.h | 2 +- test/battle/move_effect/beat_up.c | 396 +++++++++++++++++++-- 11 files changed, 428 insertions(+), 107 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 0825fe013b..588360d226 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1804,8 +1804,8 @@ .4byte \failInstr .endm - .macro jumpifcriticalhit failInstr:req - callnative BS_JumpIfCriticalHit + .macro jumpifnotcriticalhit failInstr:req + callnative BS_JumpIfNotCriticalHit .4byte \failInstr .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 93e928c880..a859d32cdf 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -4290,37 +4290,18 @@ BattleScript_DoEffectTeleport:: goto BattleScript_MoveEnd BattleScript_EffectBeatUp:: + jumpifgenconfiglowerthan GEN_CONFIG_BEAT_UP, GEN_5, BattleScript_EffectBeatUpGen3 + goto BattleScript_EffectHit + +BattleScript_EffectBeatUpGen3: attackcanceler accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE attackstring pause B_WAIT_TIME_SHORT ppreduce - setbyte gBattleCommunication, 0 -BattleScript_BeatUpLoop:: - movevaluescleanup - trydobeatup BattleScript_BeatUpEnd, BattleScript_ButItFailed + trydobeatup BattleScript_MoveEnd, BattleScript_ButItFailed printstring STRINGID_PKMNATTACK - critcalc - jumpifcriticalhit BattleScript_BeatUpAttack - manipulatedamage DMG_DOUBLED -BattleScript_BeatUpAttack:: - adjustdamage - attackanimation - waitanimation - effectivenesssound - hitanimation BS_TARGET - waitstate - healthbarupdate BS_TARGET - datahpupdate BS_TARGET - critmessage - waitmessage B_WAIT_TIME_LONG - resultmessage - waitmessage B_WAIT_TIME_LONG - tryfaintmon BS_TARGET - moveendto MOVEEND_NEXT_TARGET - goto BattleScript_BeatUpLoop -BattleScript_BeatUpEnd:: - end + goto BattleScript_HitFromCritCalc BattleScript_EffectDefenseCurl:: attackcanceler diff --git a/include/battle.h b/include/battle.h index fb9fcbc695..1c7b824fd1 100755 --- a/include/battle.h +++ b/include/battle.h @@ -739,7 +739,7 @@ struct BattleStruct u8 appearedInBattle; // Bitfield to track which Pokemon appeared in battle. Used for Burmy's form change u8 skyDropTargets[MAX_BATTLERS_COUNT]; // For Sky Drop, to account for if multiple Pokemon use Sky Drop in a double battle. // When using a move which hits multiple opponents which is then bounced by a target, we need to make sure, the move hits both opponents, the one with bounce, and the one without. - u16 beatUpSpecies[PARTY_SIZE]; + u16 beatUpSpecies[PARTY_SIZE]; // Species for Gen5+ Beat Up, otherwise party indexes u8 attackerBeforeBounce:2; u8 beatUpSlot:3; u8 pledgeMove:1; diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 2cadf215af..81dafd04df 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -96,10 +96,9 @@ enum CmdVarious // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 1 -#define DMG_DOUBLED 2 -#define DMG_1_8_TARGET_HP 3 -#define DMG_FULL_ATTACKER_HP 4 -#define DMG_BIG_ROOT 5 +#define DMG_1_8_TARGET_HP 2 +#define DMG_FULL_ATTACKER_HP 3 +#define DMG_BIG_ROOT 4 // Cmd_jumpifcantswitch #define SWITCH_IGNORE_ESCAPE_PREVENTION (1 << 7) diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 52c7401afe..9e4682c127 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -48,6 +48,7 @@ enum GenConfigTag GEN_CONFIG_PARALYZE_ELECTRIC, GEN_CONFIG_BADGE_BOOST, GEN_CONFIG_LEAF_GUARD_PREVENTS_REST, + GEN_CONFIG_BEAT_UP, GEN_CONFIG_WIDE_GUARD, GEN_CONFIG_QUICK_GUARD, GEN_CONFIG_DEFOG_EFFECT_CLEARING, diff --git a/include/generational_changes.h b/include/generational_changes.h index dc612e9d64..c85956c816 100644 --- a/include/generational_changes.h +++ b/include/generational_changes.h @@ -51,6 +51,7 @@ static const u8 sGenerationalChanges[GEN_CONFIG_COUNT] = [GEN_CONFIG_PARALYZE_ELECTRIC] = B_PARALYZE_ELECTRIC, [GEN_CONFIG_BADGE_BOOST] = B_BADGE_BOOST, [GEN_CONFIG_LEAF_GUARD_PREVENTS_REST] = B_LEAF_GUARD_PREVENTS_REST, + [GEN_CONFIG_BEAT_UP] = B_BEAT_UP, [GEN_CONFIG_WIDE_GUARD] = B_WIDE_GUARD, [GEN_CONFIG_QUICK_GUARD] = B_QUICK_GUARD, [GEN_CONFIG_DEFOG_EFFECT_CLEARING] = B_DEFOG_EFFECT_CLEARING, diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 904d8770e9..0a9bd476c4 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -786,7 +786,7 @@ static inline void CalcDynamicMoveDamage(struct DamageContext *ctx, u16 *medianD median = maximum = minimum = max(0, gBattleMons[ctx->battlerDef].hp - gBattleMons[ctx->battlerAtk].hp); break; case EFFECT_BEAT_UP: - if (B_BEAT_UP >= GEN_5) + if (GetGenConfig(GEN_CONFIG_BEAT_UP) >= GEN_5) { u32 partyCount = CalculatePartyCount(GetBattlerParty(ctx->battlerAtk)); u32 i; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 6fc03dcf26..e246c48fbb 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -9870,9 +9870,6 @@ static void Cmd_manipulatedamage(void) case DMG_CHANGE_SIGN: gBattleStruct->moveDamage[gBattlerAttacker] *= -1; break; - case DMG_DOUBLED: - gBattleStruct->moveDamage[gBattlerTarget] *= 2; - break; case DMG_1_8_TARGET_HP: gBattleStruct->moveDamage[gBattlerTarget] = GetNonDynamaxMaxHP(gBattlerTarget) / 8; if (gBattleStruct->moveDamage[gBattlerTarget] == 0) @@ -12592,48 +12589,20 @@ static void Cmd_trysetfutureattack(void) static void Cmd_trydobeatup(void) { CMD_ARGS(const u8 *endInstr, const u8 *failInstr); - struct Pokemon *party = GetBattlerParty(gBattlerAttacker); if (!IsBattlerAlive(gBattlerTarget)) { + gMultiHitCounter = 0; gBattlescriptCurrInstr = cmd->endInstr; } + else if (gBattleStruct->beatUpSlot == 0 && gMultiHitCounter == 0) + { + gBattlescriptCurrInstr = cmd->failInstr; + } else { - u8 beforeLoop = gBattleCommunication[0]; - for (;gBattleCommunication[0] < PARTY_SIZE; gBattleCommunication[0]++) - { - if (GetMonData(&party[gBattleCommunication[0]], MON_DATA_HP) - && GetMonData(&party[gBattleCommunication[0]], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE - && GetMonData(&party[gBattleCommunication[0]], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG - && !GetMonData(&party[gBattleCommunication[0]], MON_DATA_STATUS)) - break; - } - - if (gBattleCommunication[0] < PARTY_SIZE) - { - PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, gBattlerAttacker, gBattleCommunication[0]) - - gBattlescriptCurrInstr = cmd->nextInstr; - - gBattleStruct->moveDamage[gBattlerTarget] = GetSpeciesBaseAttack(GetMonData(&party[gBattleCommunication[0]], MON_DATA_SPECIES)); - gBattleStruct->moveDamage[gBattlerTarget] *= GetMovePower(gCurrentMove); - gBattleStruct->moveDamage[gBattlerTarget] *= (GetMonData(&party[gBattleCommunication[0]], MON_DATA_LEVEL) * 2 / 5 + 2); - gBattleStruct->moveDamage[gBattlerTarget] /= GetSpeciesBaseDefense(gBattleMons[gBattlerTarget].species); - gBattleStruct->moveDamage[gBattlerTarget] = (gBattleStruct->moveDamage[gBattlerTarget] / 50) + 2; - if (gProtectStructs[gBattlerAttacker].helpingHand) - gBattleStruct->moveDamage[gBattlerTarget] = gBattleStruct->moveDamage[gBattlerTarget] * 15 / 10; - - gBattleCommunication[0]++; - } - else if (beforeLoop != 0) - { - gBattlescriptCurrInstr = cmd->endInstr; - } - else - { - gBattlescriptCurrInstr = cmd->failInstr; - } + PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, gBattlerAttacker, gBattleStruct->beatUpSpecies[gBattleStruct->beatUpSlot]) + gBattlescriptCurrInstr = cmd->nextInstr; } } @@ -16447,11 +16416,11 @@ void BS_JumpIfMoveResultFlags(void) gBattlescriptCurrInstr = cmd->nextInstr; } -void BS_JumpIfCriticalHit(void) +void BS_JumpIfNotCriticalHit(void) { NATIVE_ARGS(const u8 *jumpInstr); - if (gSpecialStatuses[gBattlerTarget].criticalHit) + if (!gSpecialStatuses[gBattlerTarget].criticalHit) gBattlescriptCurrInstr = cmd->jumpInstr; else gBattlescriptCurrInstr = cmd->nextInstr; diff --git a/src/battle_util.c b/src/battle_util.c index ae522ad593..5296798db4 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -270,12 +270,34 @@ bool32 EndOrContinueWeather(void) return FALSE; } +// Gen5+ static u32 CalcBeatUpPower(void) { u32 species = gBattleStruct->beatUpSpecies[gBattleStruct->beatUpSlot++]; return (GetSpeciesBaseAttack(species) / 10) + 5; } +static s32 CalcBeatUpDamage(struct DamageContext *ctx) +{ + u32 partyIndex = gBattleStruct->beatUpSpecies[gBattleStruct->beatUpSlot++]; + struct Pokemon *party = GetBattlerParty(ctx->battlerAtk); + u32 species = GetMonData(&party[partyIndex], MON_DATA_SPECIES); + u32 levelFactor = GetMonData(&party[partyIndex], MON_DATA_LEVEL) * 2 / 5 + 2; + s32 dmg = GetSpeciesBaseAttack(species); + + dmg *= GetMovePower(ctx->move); + dmg *= levelFactor; + dmg /= GetSpeciesBaseDefense(gBattleMons[ctx->battlerDef].species); + dmg = (dmg / 50) + 2; + + if (gProtectStructs[ctx->battlerAtk].helpingHand) + dmg = dmg * 15 / 10; + if (ctx->isCrit) + dmg *= 2; + + return dmg; +} + static bool32 ShouldTeraShellDistortTypeMatchups(u32 move, u32 battlerDef, u32 abilityDef) { if (!gSpecialStatuses[battlerDef].distortedTypeMatchups @@ -2480,11 +2502,13 @@ static enum MoveCanceler CancelerMultihitMoves(void) PREPARE_BYTE_NUMBER_BUFFER(gBattleScripting.multihitString, 3, 0) } - else if (B_BEAT_UP >= GEN_5 && GetMoveEffect(gCurrentMove) == EFFECT_BEAT_UP) + else if (GetMoveEffect(gCurrentMove) == EFFECT_BEAT_UP) { struct Pokemon* party = GetBattlerParty(gBattlerAttacker); int i; gBattleStruct->beatUpSlot = 0; + gMultiHitCounter = 0; + memset(gBattleStruct->beatUpSpecies, 0xFF, sizeof(gBattleStruct->beatUpSpecies)); for (i = 0; i < PARTY_SIZE; i++) { @@ -2494,12 +2518,14 @@ static enum MoveCanceler CancelerMultihitMoves(void) && !GetMonData(&party[i], MON_DATA_IS_EGG) && !GetMonData(&party[i], MON_DATA_STATUS)) { - gBattleStruct->beatUpSpecies[gBattleStruct->beatUpSlot++] = species; + if (GetGenConfig(GEN_CONFIG_BEAT_UP) >= GEN_5) + gBattleStruct->beatUpSpecies[gMultiHitCounter] = species; + else + gBattleStruct->beatUpSpecies[gMultiHitCounter] = i; gMultiHitCounter++; } } - gBattleStruct->beatUpSlot = 0; PREPARE_BYTE_NUMBER_BUFFER(gBattleScripting.multihitString, 1, 0) } else @@ -8267,7 +8293,7 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx) basePower *= 2; break; case EFFECT_BEAT_UP: - if (B_BEAT_UP >= GEN_5) + if (GetGenConfig(GEN_CONFIG_BEAT_UP) >= GEN_5) basePower = CalcBeatUpPower(); break; case EFFECT_PSYBLADE: @@ -9508,6 +9534,12 @@ s32 DoFixedDamageMoveCalc(struct DamageContext *ctx) case EFFECT_FINAL_GAMBIT: dmg = GetNonDynamaxHP(ctx->battlerAtk); break; + case EFFECT_BEAT_UP: + if (GetGenConfig(GEN_CONFIG_BEAT_UP) < GEN_5) + dmg = CalcBeatUpDamage(ctx); + else + return INT32_MAX; + break; default: return INT32_MAX; } diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index 51bbdba0eb..88d4075570 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -791,7 +791,7 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = [EFFECT_BEAT_UP] = { - .battleScript = (B_BEAT_UP >= GEN_5) ? BattleScript_EffectHit : BattleScript_EffectBeatUp, + .battleScript = BattleScript_EffectBeatUp, .battleTvScore = 2, }, diff --git a/test/battle/move_effect/beat_up.c b/test/battle/move_effect/beat_up.c index e584ccc0f9..1abc7bb958 100644 --- a/test/battle/move_effect/beat_up.c +++ b/test/battle/move_effect/beat_up.c @@ -1,10 +1,18 @@ #include "global.h" #include "test/battle.h" -// General +// TODO: Beat Up's strikes have each an independent chance of a critical hit +// Unconfirmed by Bulbapedia +// - Technician interacion + SINGLE_BATTLE_TEST("Beat Up hits the target for each non-fainted, non-statused member in the party") { + u32 gen; + PARAMETRIZE { gen = GEN_3; } + PARAMETRIZE { gen = GEN_5; } + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, gen); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WYNAUT); PLAYER(SPECIES_PICHU) @@ -21,37 +29,367 @@ SINGLE_BATTLE_TEST("Beat Up hits the target for each non-fainted, non-statused m NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); MESSAGE("The Pokémon was hit 4 time(s)!"); } THEN { - EXPECT_EQ(gBattleStruct->beatUpSpecies[0], SPECIES_WOBBUFFET); - EXPECT_EQ(gBattleStruct->beatUpSpecies[1], SPECIES_WYNAUT); - EXPECT_EQ(gBattleStruct->beatUpSpecies[2], SPECIES_PICHU); - EXPECT_EQ(gBattleStruct->beatUpSpecies[3], SPECIES_RAICHU); + if (gen == GEN_5) { + EXPECT_EQ(gBattleStruct->beatUpSpecies[0], SPECIES_WOBBUFFET); + EXPECT_EQ(gBattleStruct->beatUpSpecies[1], SPECIES_WYNAUT); + EXPECT_EQ(gBattleStruct->beatUpSpecies[2], SPECIES_PICHU); + EXPECT_EQ(gBattleStruct->beatUpSpecies[3], SPECIES_RAICHU); + } + else { + EXPECT_EQ(gBattleStruct->beatUpSpecies[0], 0); + EXPECT_EQ(gBattleStruct->beatUpSpecies[1], 1); + EXPECT_EQ(gBattleStruct->beatUpSpecies[2], 2); + EXPECT_EQ(gBattleStruct->beatUpSpecies[3], 4); + } } } -TO_DO_BATTLE_TEST("Beat Up doesn't consider Comatose as a status") -TO_DO_BATTLE_TEST("Beat Up's strikes have each an independent chance of a critical hit"); +SINGLE_BATTLE_TEST("Beat Up doesn't consider Comatose as a status") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_COMATOSE); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WYNAUT) { Status1(STATUS1_SLEEP); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + MESSAGE("The Pokémon was hit 2 time(s)!"); + } +} -// B_BEAT_UP Gen2-4 -TO_DO_BATTLE_TEST("Beat Up lists each party member's name"); -TO_DO_BATTLE_TEST("Beat Up's damage is typeless"); -TO_DO_BATTLE_TEST("Beat Up's damage doesn't consider STAB"); -TO_DO_BATTLE_TEST("Beat Up's last strike-only can trigger King's Rock"); -TO_DO_BATTLE_TEST("Beat Up's base power is the same for each strike"); -TO_DO_BATTLE_TEST("Beat Up's damage is determined by each striking Pokémon's base attack and level and the target's defense"); -TO_DO_BATTLE_TEST("Beat Up ignores stat stage changes"); //eg. Swords Dance -TO_DO_BATTLE_TEST("Beat Up ignores Huge Power"); -TO_DO_BATTLE_TEST("Beat Up ignores Choice Band"); +SINGLE_BATTLE_TEST("Beat Up doesn't list party member's name (Gen5+)") +{ + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_5); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + NONE_OF { + MESSAGE("Wobbuffet's attack!"); + MESSAGE("Wynaut's attack!"); + } + MESSAGE("The Pokémon was hit 2 time(s)!"); + } +} -// B_BEAT_UP Gen5+ -TO_DO_BATTLE_TEST("Beat Up doesn't list party member's name"); -TO_DO_BATTLE_TEST("Beat Up's damage is Dark-typed"); -TO_DO_BATTLE_TEST("Beat Up's damage receives STAB"); -TO_DO_BATTLE_TEST("Beat Up's can trigger King's Rock on all strikes"); -TO_DO_BATTLE_TEST("Beat Up's base power is determined by each striking Pokémon"); -TO_DO_BATTLE_TEST("Beat Up's damage is determined by the user's attack and the target's defense"); -TO_DO_BATTLE_TEST("Beat Up's damage considers stat stage changes"); //eg. Swords Dance -TO_DO_BATTLE_TEST("Beat Up's damage considers Huge Power"); -TO_DO_BATTLE_TEST("Beat Up's damage considers Choice Band"); +SINGLE_BATTLE_TEST("Beat Up's damage is Dark-typed (Gen5+)", s16 damage) +{ + bool32 targetIsFairy; + PARAMETRIZE { targetIsFairy = FALSE; } + PARAMETRIZE { targetIsFairy = TRUE; } -// Unconfirmed by Bulbapedia -// - Technician interacion + ASSUME(GetMoveType(MOVE_BEAT_UP) == TYPE_DARK); + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_5); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(targetIsFairy ? SPECIES_SYLVEON : SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (targetIsFairy) + EXPECT_LT(results[i].damage, results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Beat Up's base power is determined by each striking Pokémon (Gen5+)") +{ + s16 firstHit, secondHit; + + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_5); + PLAYER(SPECIES_SHUCKLE); + PLAYER(SPECIES_DEOXYS_ATTACK); + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + OPPONENT(SPECIES_BLISSEY); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &firstHit); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &secondHit); + } THEN { + EXPECT_LT(firstHit, secondHit); + } +} + +SINGLE_BATTLE_TEST("Beat Up's damage considers stat stage changes (Gen5+)", s16 damage) +{ + bool32 boosted; + PARAMETRIZE { boosted = FALSE; } + PARAMETRIZE { boosted = TRUE; } + + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_5); + PLAYER(SPECIES_UMBREON); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { if (boosted) { MOVE(player, MOVE_SWORDS_DANCE); } else { MOVE(player, MOVE_CELEBRATE); } } + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + if (boosted) + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (boosted) + EXPECT_GT(results[i].damage, results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Beat Up's damage considers Huge Power and Choice Band (Gen5+)", s16 damage) +{ + u16 ability; + u16 item; + + PARAMETRIZE { ability = ABILITY_THICK_FAT; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_HUGE_POWER; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_THICK_FAT; item = ITEM_CHOICE_BAND; } + + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_5); + PLAYER(SPECIES_AZUMARILL) { Ability(ability); Item(item); Moves(MOVE_BEAT_UP); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (i == 1) + EXPECT_GT(results[i].damage, results[0].damage); + if (i == 2) + EXPECT_GT(results[i].damage, results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Beat Up lists each party member's name") +{ + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_PIKACHU); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + MESSAGE("Wobbuffet's attack!"); + MESSAGE("Wynaut's attack!"); + NOT MESSAGE("Wynaut's attack!"); + MESSAGE("Pikachu's attack!"); + } +} + +SINGLE_BATTLE_TEST("Beat Up's damage is typeless", s16 damage) +{ + u16 defender = SPECIES_WOBBUFFET; + u16 type1, type2; + + PARAMETRIZE { defender = SPECIES_BLISSEY; } // Normal + PARAMETRIZE { defender = SPECIES_MACHAMP; } // Fighting + PARAMETRIZE { defender = SPECIES_TORNADUS; } // Flying + PARAMETRIZE { defender = SPECIES_GRIMER; } // Poison + PARAMETRIZE { defender = SPECIES_SANDSHREW; } // Ground + PARAMETRIZE { defender = SPECIES_NOSEPASS; } // Rock + PARAMETRIZE { defender = SPECIES_CATERPIE; } // Bug + PARAMETRIZE { defender = SPECIES_DUSKULL; } // Ghost + PARAMETRIZE { defender = SPECIES_REGISTEEL; } // Steel + PARAMETRIZE { defender = SPECIES_CHIMCHAR; } // Fire + PARAMETRIZE { defender = SPECIES_WARTORTLE; } // Water + PARAMETRIZE { defender = SPECIES_TANGELA; } // Grass + PARAMETRIZE { defender = SPECIES_PIKACHU; } // Electric + PARAMETRIZE { defender = SPECIES_ABRA; } // Psychic + PARAMETRIZE { defender = SPECIES_SNORUNT; } // Ice + PARAMETRIZE { defender = SPECIES_BAGON; } // Dragon + PARAMETRIZE { defender = SPECIES_UMBREON; } // Dark + PARAMETRIZE { defender = SPECIES_SYLVEON; } // Fairy + + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + type1 = GetSpeciesType(defender, 0); + type2 = GetSpeciesType(defender, 1); + ASSUME(type2 == type1 || type2 == TYPE_MYSTERY); // Ensure monotype targets + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(defender); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + NONE_OF { + MESSAGE("It's super effective!"); + MESSAGE("It's not very effective..."); + MESSAGE("It doesn't affect"); + } + } THEN { + EXPECT_GT(results[i].damage, 0); + } +} + +SINGLE_BATTLE_TEST("Beat Up's damage doesn't consider STAB") +{ + s16 damage; + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + damage = 0; + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { HP(0); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + HP_BAR(opponent, captureDamage: &damage); + } THEN { + // Raw damage: baseAtk 33 * basePower 1 * levelFactor ((100 * 2 / 5) + 2 = 42) = 1386 + // Divide by baseDef 58 -> 23 (floor); 23/50 + 2 = 2; + u16 expected = 2; + EXPECT_EQ(damage, expected); + } +} + +SINGLE_BATTLE_TEST("Beat Up's base power is the same for each strike") +{ + s16 firstHit, secondHit; + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + firstHit = 0; + secondHit = 0; + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &firstHit); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &secondHit); + } THEN { + EXPECT_EQ(firstHit, secondHit); + } +} + +SINGLE_BATTLE_TEST("Beat Up's damage is determined by each striking Pokémon's base attack and level and the target's defense") +{ + s16 shuckleHit, deoxysHit; + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + shuckleHit = 0; + deoxysHit = 0; + PLAYER(SPECIES_SHUCKLE); + PLAYER(SPECIES_DEOXYS_ATTACK); + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + OPPONENT(SPECIES_BLISSEY); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &shuckleHit); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &deoxysHit); + } THEN { + // Shuckle: baseAtk 10 * basePower 1 * levelFactor 42 = 420; / baseDef 10 -> 42; 42/50 + 2 = 2 + u16 shuckleDmg = 2; + // Deoxys-A: baseAtk 180 * basePower 1 * levelFactor 42 = 7560; / baseDef 10 -> 756; 756/50 + 2 = 17 + u16 deoxysDmg = 17; + EXPECT_EQ(shuckleHit, shuckleDmg); + EXPECT_EQ(deoxysHit, deoxysDmg); + EXPECT_LT(shuckleHit, deoxysHit); + } +} + +SINGLE_BATTLE_TEST("Beat Up ignores stat stage changes", s16 damage) +{ + bool32 boosted; + PARAMETRIZE { boosted = FALSE; } + PARAMETRIZE { boosted = TRUE; } + + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { if (boosted) { MOVE(player, MOVE_SWORDS_DANCE); } else { MOVE(player, MOVE_CELEBRATE); } } + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + if (boosted) + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (boosted) + EXPECT_EQ(results[i].damage, results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Beat Up ignores Huge Power", s16 damage) +{ + u16 ability; + + PARAMETRIZE { ability = ABILITY_THICK_FAT; } + PARAMETRIZE { ability = ABILITY_HUGE_POWER; } + + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + PLAYER(SPECIES_AZUMARILL) { Ability(ability); Moves(MOVE_BEAT_UP); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (ability == ABILITY_HUGE_POWER) + EXPECT_EQ(results[i].damage, results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Beat Up ignores Choice Band", s16 damage) +{ + u16 item; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_CHOICE_BAND; } + + GIVEN { + WITH_CONFIG(GEN_CONFIG_BEAT_UP, GEN_3); + PLAYER(SPECIES_URSARING) { Item(item); Moves(MOVE_BEAT_UP); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (item == ITEM_CHOICE_BAND) + EXPECT_EQ(results[i].damage, results[0].damage); + } +}