From a60df1f71b39882df041bc2ca525e5c6f4dc576a Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Thu, 18 Dec 2025 19:02:35 +0100 Subject: [PATCH 01/13] Remove another unused deoxys function (#8576) --- include/pokemon.h | 1 - src/pokemon.c | 57 ----------------------------------------------- 2 files changed, 58 deletions(-) diff --git a/include/pokemon.h b/include/pokemon.h index bc6af2a48e..9ca2743300 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -723,7 +723,6 @@ void CreateBattleTowerMon_HandleLevel(struct Pokemon *mon, struct BattleTowerPok void CreateApprenticeMon(struct Pokemon *mon, const struct Apprentice *src, u8 monId); void CreateMonWithEVSpreadNatureOTID(struct Pokemon *mon, u16 species, u8 level, u8 nature, u8 fixedIV, u8 evSpread, u32 otId); void ConvertPokemonToBattleTowerPokemon(struct Pokemon *mon, struct BattleTowerPokemon *dest); -bool8 ShouldIgnoreDeoxysForm(u8 caseId, u8 battler); u16 GetUnionRoomTrainerPic(void); enum TrainerClassID GetUnionRoomTrainerClass(void); void CreateEnemyEventMon(void); diff --git a/src/pokemon.c b/src/pokemon.c index 2f237cba6f..9567c22636 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -1554,63 +1554,6 @@ static void CreateEventMon(struct Pokemon *mon, u16 species, u8 level, u8 fixedI SetMonData(mon, MON_DATA_MODERN_FATEFUL_ENCOUNTER, &isModernFatefulEncounter); } -// If FALSE, should load this game's Deoxys form. If TRUE, should load normal Deoxys form -bool8 ShouldIgnoreDeoxysForm(u8 caseId, u8 battler) -{ - switch (caseId) - { - case 0: - default: - return FALSE; - case 1: // Player's side in battle - if (!(gBattleTypeFlags & BATTLE_TYPE_MULTI)) - return FALSE; - if (!gMain.inBattle) - return FALSE; - if (gLinkPlayers[GetMultiplayerId()].id == battler) - return FALSE; - break; - case 2: - break; - case 3: // Summary Screen - if (!(gBattleTypeFlags & BATTLE_TYPE_MULTI)) - return FALSE; - if (!gMain.inBattle) - return FALSE; - if (battler == 1 || battler == 4 || battler == 5) - return TRUE; - return FALSE; - case 4: - break; - case 5: // In move animation, e.g. in Role Play or Snatch - if (gBattleTypeFlags & BATTLE_TYPE_LINK) - { - if (!gMain.inBattle) - return FALSE; - if (gBattleTypeFlags & BATTLE_TYPE_MULTI) - { - if (gLinkPlayers[GetMultiplayerId()].id == battler) - return FALSE; - } - else - { - if (IsOnPlayerSide(battler)) - return FALSE; - } - } - else - { - if (!gMain.inBattle) - return FALSE; - if (IsOnPlayerSide(battler)) - return FALSE; - } - break; - } - - return TRUE; -} - u16 GetUnionRoomTrainerPic(void) { u8 linkId; From cd856cfc403cdbfb572e8a35b6bc53a60a7f5208 Mon Sep 17 00:00:00 2001 From: Salem <65783283+u8-Salem@users.noreply.github.com> Date: Thu, 18 Dec 2025 21:15:28 +0100 Subject: [PATCH 02/13] fix CONTRIBUTING.md (#8577) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b89016fddd..88ef358dbb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ The best bug reports have enough information that we won't have to contact you f - A maintainer will [label](https://github.com/rh-hideout/pokeemerald-expansion/labels) the bug report. - A maintainer will try to reproduce the bug with your provided steps. - - If there are no reproduction steps or no obvious way to reproduce the issue, somebody will ask you for those steps. Until the bug can be reproduced, the bug will retain the `bug:unconfirmed` label. Unconfirmed bugs are less likely get fixed. + - If there are no reproduction steps or no obvious way to reproduce the issue, somebody will ask you for those steps. Until the bug can be reproduced, the bug will retain the `bug:unconfirmed` label. Unconfirmed bugs are less likely to get fixed. - If the team is able to reproduce the bug, it will be labeled `bug:confirmed`, and the bug will be left to be [fixed by someone](#Pull-Requests). - If the issue is particularly game-breaking, a maintainer will add it to a future version's [milestone](), meaning that version will not be released until the problem is solved. From fe6f72ec52696d6897bcbbc46a0a197e3d942955 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Thu, 18 Dec 2025 22:56:42 +0100 Subject: [PATCH 03/13] Fix party pool oob (#8581) --- src/trainer_pools.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/trainer_pools.c b/src/trainer_pools.c index 6f0e57fdb2..faf93fcc9e 100644 --- a/src/trainer_pools.c +++ b/src/trainer_pools.c @@ -167,6 +167,10 @@ static u32 PickMonFromPool(const struct Trainer *trainer, u8 *poolIndexArray, u3 // If no mon has been found yet continue looking if (monIndex == POOL_SLOT_DISABLED) monIndex = pickFunctions.OtherFunction(trainer, poolIndexArray, partyIndex, monsCount, battleTypeFlags, rules); + // If a mon still hasn't been found, return POOL_SLOT_DISABLED which makes party generation default to regular party generation + if (monIndex == POOL_SLOT_DISABLED) + return monIndex; + u32 chosenTags = trainer->party[monIndex].tags; u16 chosenSpecies = trainer->party[monIndex].species; u16 chosenItem = trainer->party[monIndex].heldItem; From b78fccdb663eb51a3ffdc5cd6cfe6e53f0168026 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Thu, 18 Dec 2025 19:24:52 -0300 Subject: [PATCH 04/13] `B_STURDY` config test (#8565) --- include/constants/generational_changes.h | 2 +- src/battle_ai_switch_items.c | 4 ++-- src/battle_ai_util.c | 6 ++--- src/battle_script_commands.c | 2 +- test/battle/ability/sturdy.c | 29 ++++++++++++++++++++---- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index f94b7e8309..7d0699d479 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -132,7 +132,7 @@ F(SYNCHRONIZE_TOXIC, synchronizeToxic, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(UPDATED_INTIMIDATE, updatedIntimidate, (u32, GEN_COUNT - 1)) \ F(OBLIVIOUS_TAUNT, obliviousTaunt, (u32, GEN_COUNT - 1)) \ - F(STURDY, sturdy, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ + F(STURDY, sturdy, (u32, GEN_COUNT - 1)) \ F(PLUS_MINUS_INTERACTION, plusMinusInteraction, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(WEATHER_FORMS, weatherForms, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(SYMBIOSIS_GEMS, symbiosisGems, (u32, GEN_COUNT - 1)) \ diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 5b924d23e5..1c327879cd 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -298,7 +298,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) // Check if mon gets one shot if (maxDamageTaken > gBattleMons[battler].hp - && !(gItemsInfo[gBattleMons[battler].item].holdEffect == HOLD_EFFECT_FOCUS_SASH || (!IsMoldBreakerTypeAbility(opposingBattler, gAiLogicData->abilities[opposingBattler]) && B_STURDY >= GEN_5 && aiAbility == ABILITY_STURDY))) + && !(gItemsInfo[gBattleMons[battler].item].holdEffect == HOLD_EFFECT_FOCUS_SASH || (!IsMoldBreakerTypeAbility(opposingBattler, gAiLogicData->abilities[opposingBattler]) && GetConfig(CONFIG_STURDY) >= GEN_5 && aiAbility == ABILITY_STURDY))) { getsOneShot = TRUE; } @@ -1852,7 +1852,7 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, u32 battler) currentHP = currentHP - damageTaken; // One shot prevention effects - if (damageTaken >= maxHP && startingHP == maxHP && (heldItemEffect == HOLD_EFFECT_FOCUS_SASH || (!opponentCanBreakMold && B_STURDY >= GEN_5 && ability == ABILITY_STURDY)) && hitsToKO < 1) + if (damageTaken >= maxHP && startingHP == maxHP && (heldItemEffect == HOLD_EFFECT_FOCUS_SASH || (!opponentCanBreakMold && GetConfig(CONFIG_STURDY) >= GEN_5 && ability == ABILITY_STURDY)) && hitsToKO < 1) currentHP = 1; // If mon is still alive, apply weather impact first, as it might KO the mon before it can heal with its item (order is weather -> item -> status) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 413bbcc8ac..a66a24e2e7 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -1469,7 +1469,7 @@ bool32 CanEndureHit(u32 battler, u32 battlerTarget, u32 move) if (!DoesBattlerIgnoreAbilityChecks(battler, gAiLogicData->abilities[battler], move)) { - if (B_STURDY >= GEN_5 && gAiLogicData->abilities[battlerTarget] == ABILITY_STURDY) + if (GetConfig(CONFIG_STURDY) >= GEN_5 && gAiLogicData->abilities[battlerTarget] == ABILITY_STURDY) return TRUE; if (IsMimikyuDisguised(battlerTarget)) return TRUE; @@ -3351,7 +3351,7 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, enum Ability defAbility if (!IsBattleMoveStatus(move) && ((gAiLogicData->shouldSwitch & (1u << battlerAtk)) || (AI_BattlerAtMaxHp(battlerDef) && (gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_FOCUS_SASH - || (B_STURDY >= GEN_5 && defAbility == ABILITY_STURDY) + || (GetConfig(CONFIG_STURDY) >= GEN_5 && defAbility == ABILITY_STURDY) || defAbility == ABILITY_MULTISCALE || defAbility == ABILITY_SHADOW_SHIELD)))) return SHOULD_PIVOT; // pivot to break sash/sturdy/multiscale @@ -3359,7 +3359,7 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, enum Ability defAbility else if (!hasStatBoost) { if (!IsBattleMoveStatus(move) && (AI_BattlerAtMaxHp(battlerDef) && (gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_FOCUS_SASH - || (B_STURDY >= GEN_5 && defAbility == ABILITY_STURDY) + || (GetConfig(CONFIG_STURDY) >= GEN_5 && defAbility == ABILITY_STURDY) || defAbility == ABILITY_MULTISCALE || defAbility == ABILITY_SHADOW_SHIELD))) return SHOULD_PIVOT; // pivot to break sash/sturdy/multiscale diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 92c4bff67c..98eb569ee2 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1929,7 +1929,7 @@ static void Cmd_adjustdamage(void) gLastUsedItem = gBattleMons[battlerDef].item; gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_FOE_HUNG_ON; } - else if (B_STURDY >= GEN_5 && GetBattlerAbility(battlerDef) == ABILITY_STURDY && IsBattlerAtMaxHp(battlerDef)) + else if (GetConfig(CONFIG_STURDY) >= GEN_5 && GetBattlerAbility(battlerDef) == ABILITY_STURDY && IsBattlerAtMaxHp(battlerDef)) { enduredHit |= 1u << battlerDef; RecordAbilityBattle(battlerDef, ABILITY_STURDY); diff --git a/test/battle/ability/sturdy.c b/test/battle/ability/sturdy.c index 5ba7e16ea8..7087de3a02 100644 --- a/test/battle/ability/sturdy.c +++ b/test/battle/ability/sturdy.c @@ -18,18 +18,37 @@ SINGLE_BATTLE_TEST("Sturdy prevents OHKO moves") } } -SINGLE_BATTLE_TEST("Sturdy prevents OHKOs") +SINGLE_BATTLE_TEST("Sturdy prevents OHKOs (Gen5+)") { + u32 config; + PARAMETRIZE { config = GEN_4; } + PARAMETRIZE { config = GEN_5; } GIVEN { + WITH_CONFIG(CONFIG_STURDY, config); PLAYER(SPECIES_GEODUDE) { Ability(ABILITY_STURDY); MaxHP(100); HP(100); } + PLAYER(SPECIES_GEODUDE); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_SEISMIC_TOSS); } + TURN { + MOVE(opponent, MOVE_SEISMIC_TOSS); + if (config < GEN_5) { + SEND_OUT(player, 1); + } + } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SEISMIC_TOSS, opponent); - HP_BAR(player, hp: 1); - ABILITY_POPUP(player, ABILITY_STURDY); - MESSAGE("Geodude endured the hit using Sturdy!"); + if (config >= GEN_5) { + HP_BAR(player, hp: 1); + ABILITY_POPUP(player, ABILITY_STURDY); + MESSAGE("Geodude endured the hit using Sturdy!"); + } else { + HP_BAR(player, hp: 0); + NONE_OF { + ABILITY_POPUP(player, ABILITY_STURDY); + MESSAGE("Geodude endured the hit using Sturdy!"); + } + SEND_IN_MESSAGE("Geodude"); + } } } From 6e529cf221cfa2b3e11414f3b3f73bc4fb142378 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Thu, 18 Dec 2025 20:45:54 -0300 Subject: [PATCH 05/13] `B_KLUTZ_FLING_INTERACTION` config test (#8568) --- include/constants/generational_changes.h | 2 +- src/battle_util.c | 2 +- test/battle/move_effect/fling.c | 12 +++++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 7d0699d479..a22b686d77 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -64,7 +64,7 @@ F(UPDATED_MOVE_FLAGS, updatedMoveFlags, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(PHYSICAL_SPECIAL_SPLIT, physicalSpecialSplit, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(RECOIL_IF_MISS_DMG, recoilIfMissDmg, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ - F(KLUTZ_FLING_INTERACTION, klutzFlingInteraction, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ + F(KLUTZ_FLING_INTERACTION, klutzFlingInteraction, (u32, GEN_COUNT - 1)) \ F(UPDATED_CONVERSION, updatedConversion, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(UPDATED_CONVERSION_2, updatedConversion2, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(PP_REDUCED_BY_SPITE, ppReducedBySpite, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ diff --git a/src/battle_util.c b/src/battle_util.c index 888580f3fd..975e1f094c 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -9743,7 +9743,7 @@ bool32 CanFling(u32 battler) u16 item = gBattleMons[battler].item; if (item == ITEM_NONE - || (B_KLUTZ_FLING_INTERACTION >= GEN_5 && GetBattlerAbility(battler) == ABILITY_KLUTZ) + || (GetConfig(CONFIG_KLUTZ_FLING_INTERACTION) >= GEN_5 && GetBattlerAbility(battler) == ABILITY_KLUTZ) || gFieldStatuses & STATUS_FIELD_MAGIC_ROOM || gBattleMons[battler].volatiles.embargo || GetFlingPowerFromItemId(item) == 0 diff --git a/test/battle/move_effect/fling.c b/test/battle/move_effect/fling.c index 2046ebe638..012485abe8 100644 --- a/test/battle/move_effect/fling.c +++ b/test/battle/move_effect/fling.c @@ -56,22 +56,24 @@ SINGLE_BATTLE_TEST("Fling fails if Pokémon is under the effects of Embargo or M } } -SINGLE_BATTLE_TEST("Fling fails for Pokémon with Klutz ability") +SINGLE_BATTLE_TEST("Fling fails for Pokémon with Klutz ability (Gen5+)") { enum Ability ability; + u32 config; - PARAMETRIZE {ability = ABILITY_KLUTZ; } - PARAMETRIZE {ability = ABILITY_RUN_AWAY; } + PARAMETRIZE { ability = ABILITY_RUN_AWAY; config = GEN_4; } + PARAMETRIZE { ability = ABILITY_KLUTZ; config = GEN_4; } + PARAMETRIZE { ability = ABILITY_KLUTZ; config = GEN_5; } GIVEN { - ASSUME(B_KLUTZ_FLING_INTERACTION >= GEN_5); + WITH_CONFIG(CONFIG_KLUTZ_FLING_INTERACTION, config); PLAYER(SPECIES_BUNEARY) { Item(ITEM_RAZOR_CLAW); Ability(ability); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_FLING); } } SCENE { MESSAGE("Buneary used Fling!"); - if (ability != ABILITY_KLUTZ) { + if (ability != ABILITY_KLUTZ || config == GEN_4) { ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); HP_BAR(opponent); } else { From be9879f94a2774b384ec1a4e882e52c880ac70d9 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Thu, 18 Dec 2025 20:48:54 -0300 Subject: [PATCH 06/13] `B_IRON_BALL` config test (#8569) --- include/constants/generational_changes.h | 2 +- src/battle_util.c | 2 +- test/battle/hold_effect/iron_ball.c | 13 ++++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index a22b686d77..701330ae10 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -176,7 +176,7 @@ F(SAFARI_BALL_MODIFIER, safariBallModifier, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(FRIEND_BALL_MODIFIER, friendBallModifier, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(SERENE_GRACE_BOOST, sereneGraceBoost, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ - F(IRON_BALL, ironBall, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ + F(IRON_BALL, ironBall, (u32, GEN_COUNT - 1)) \ /* Weather settings */ \ F(ABILITY_WEATHER, abilityWeather, (u32, GEN_COUNT - 1)) \ F(SANDSTORM_SPDEF_BOOST, sandstormSpDefBoost, (u32, GEN_COUNT - 1)) \ diff --git a/src/battle_util.c b/src/battle_util.c index 975e1f094c..ea4c918d40 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -8798,7 +8798,7 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(struct DamageCont } // Iron Ball ignores type modifiers for flying-type mons if it is the only source of grounding - if (B_IRON_BALL >= GEN_5 + if (GetConfig(CONFIG_IRON_BALL) >= GEN_5 && ctx->moveType == TYPE_GROUND && ctx->holdEffectDef == HOLD_EFFECT_IRON_BALL && IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_FLYING) diff --git a/test/battle/hold_effect/iron_ball.c b/test/battle/hold_effect/iron_ball.c index 134c47d9af..90c370e8f3 100644 --- a/test/battle/hold_effect/iron_ball.c +++ b/test/battle/hold_effect/iron_ball.c @@ -5,17 +5,24 @@ ASSUMPTIONS{ ASSUME(gItemsInfo[ITEM_IRON_BALL].holdEffect == HOLD_EFFECT_IRON_BALL); } -SINGLE_BATTLE_TEST("Ground-type moves do neutral damage to non-grounded Flying types holding Iron Ball regardless of other typings") //gen5+ only +SINGLE_BATTLE_TEST("Ground-type moves do neutral damage to non-grounded Flying types holding Iron Ball regardless of other typings (Gen5+)") { - ASSUME(B_IRON_BALL >= GEN_5); + u32 config; + PARAMETRIZE { config = GEN_4; } + PARAMETRIZE { config = GEN_5; } GIVEN { + WITH_CONFIG(CONFIG_IRON_BALL, config); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_SKARMORY) { Item(ITEM_IRON_BALL); }; } WHEN { TURN { MOVE(player, MOVE_EARTHQUAKE); }; } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, player); - NONE_OF { + if (config >= GEN_5) { + NONE_OF { + MESSAGE("It's super effective!"); + } + } else { MESSAGE("It's super effective!"); } } From dc5ed3a85b8a002cb259b639c41c91af3347c832 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Thu, 18 Dec 2025 20:52:15 -0300 Subject: [PATCH 07/13] `B_SYNCHRONIZE_TOXIC` config test (#8572) --- include/constants/generational_changes.h | 2 +- src/battle_util.c | 4 ++-- test/battle/ability/synchronize.c | 11 ++++++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 701330ae10..4a9cc47dc0 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -129,7 +129,7 @@ F(SHADOW_TAG_ESCAPE, shadowTagEscape, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(MOODY_ACC_EVASION, moodyAccEvasion, (u32, GEN_COUNT - 1)) \ F(FLASH_FIRE_FROZEN, flashFireFrozen, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ - F(SYNCHRONIZE_TOXIC, synchronizeToxic, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ + F(SYNCHRONIZE_TOXIC, synchronizeToxic, (u32, GEN_COUNT - 1)) \ F(UPDATED_INTIMIDATE, updatedIntimidate, (u32, GEN_COUNT - 1)) \ F(OBLIVIOUS_TAUNT, obliviousTaunt, (u32, GEN_COUNT - 1)) \ F(STURDY, sturdy, (u32, GEN_COUNT - 1)) \ diff --git a/src/battle_util.c b/src/battle_util.c index ea4c918d40..90532e2360 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5500,7 +5500,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab gBattleScripting.battler = gBattlerAbility = gBattlerTarget; RecordAbilityBattle(gBattlerTarget, ABILITY_SYNCHRONIZE); - if (B_SYNCHRONIZE_TOXIC < GEN_5 && gBattleStruct->synchronizeMoveEffect == MOVE_EFFECT_TOXIC) + if (GetConfig(CONFIG_SYNCHRONIZE_TOXIC) < GEN_5 && gBattleStruct->synchronizeMoveEffect == MOVE_EFFECT_TOXIC) gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_POISON; if (CanSetNonVolatileStatus( @@ -5530,7 +5530,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab gBattleScripting.battler = gBattlerAbility = gBattlerAttacker; RecordAbilityBattle(gBattlerAttacker, ABILITY_SYNCHRONIZE); - if (B_SYNCHRONIZE_TOXIC < GEN_5 && gBattleStruct->synchronizeMoveEffect == MOVE_EFFECT_TOXIC) + if (GetConfig(CONFIG_SYNCHRONIZE_TOXIC) < GEN_5 && gBattleStruct->synchronizeMoveEffect == MOVE_EFFECT_TOXIC) gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_POISON; if (CanSetNonVolatileStatus( diff --git a/test/battle/ability/synchronize.c b/test/battle/ability/synchronize.c index 8e92936e41..8604814fab 100644 --- a/test/battle/ability/synchronize.c +++ b/test/battle/ability/synchronize.c @@ -3,11 +3,13 @@ SINGLE_BATTLE_TEST("Synchronize will mirror back non volatile status back at opposing mon") { - + u32 config; + PARAMETRIZE { config = GEN_4; } + PARAMETRIZE { config = GEN_5; } GIVEN { ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); - ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + WITH_CONFIG(CONFIG_SYNCHRONIZE_TOXIC, config); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_ABRA) { Ability(ABILITY_SYNCHRONIZE); } } WHEN { @@ -18,7 +20,10 @@ SINGLE_BATTLE_TEST("Synchronize will mirror back non volatile status back at opp STATUS_ICON(opponent, badPoison: TRUE); ABILITY_POPUP(opponent, ABILITY_SYNCHRONIZE); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); - STATUS_ICON(player, badPoison: TRUE); + if (config >= GEN_5) + STATUS_ICON(player, badPoison: TRUE); + else + STATUS_ICON(player, poison: TRUE); } } From 914a481308f2d647c10b45d17728571725252e74 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Thu, 18 Dec 2025 20:53:35 -0300 Subject: [PATCH 08/13] `B_BURN_DAMAGE` config tests (#8574) --- include/constants/generational_changes.h | 2 +- src/battle_ai_switch_items.c | 4 ++-- src/battle_end_turn.c | 4 ++-- test/battle/status1/burn.c | 9 ++++++--- test/battle/status1/frostbite.c | 11 ++++++++--- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 4a9cc47dc0..34a7c14f75 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -24,7 +24,7 @@ F(MAX_LEVEL_EV_GAINS, maxLevelEvGains, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(RECALCULATE_STATS, recalculateStats, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ /* Damage settings */ \ - F(BURN_DAMAGE, burnDamage, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ + F(BURN_DAMAGE, burnDamage, (u32, GEN_COUNT - 1)) \ F(BURN_FACADE_DMG, burnFacadeDmg, (u32, GEN_COUNT - 1)) \ F(BINDING_DAMAGE, bindingDamage, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(PSYWAVE_DMG, psywaveDmg, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 1c327879cd..31f903c8c9 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -1757,7 +1757,7 @@ static u32 GetSwitchinStatusDamage(u32 battler) { if (status & STATUS1_BURN) { - if (B_BURN_DAMAGE >= GEN_7) + if (GetConfig(CONFIG_BURN_DAMAGE) >= GEN_7) statusDamage = maxHP / 16; else statusDamage = maxHP / 8; @@ -1768,7 +1768,7 @@ static u32 GetSwitchinStatusDamage(u32 battler) } else if (status & STATUS1_FROSTBITE) { - if (B_BURN_DAMAGE >= GEN_7) + if (GetConfig(CONFIG_BURN_DAMAGE) >= GEN_7) statusDamage = maxHP / 16; else statusDamage = maxHP / 8; diff --git a/src/battle_end_turn.c b/src/battle_end_turn.c index ca4c46fa11..ef5539ebbf 100644 --- a/src/battle_end_turn.c +++ b/src/battle_end_turn.c @@ -531,7 +531,7 @@ static bool32 HandleEndTurnBurn(u32 battler) && IsBattlerAlive(battler) && !IsAbilityAndRecord(battler, ability, ABILITY_MAGIC_GUARD)) { - s32 burnDamage = GetNonDynamaxMaxHP(battler) / (B_BURN_DAMAGE >= GEN_7 ? 16 : 8); + s32 burnDamage = GetNonDynamaxMaxHP(battler) / (GetConfig(CONFIG_BURN_DAMAGE) >= GEN_7 ? 16 : 8); if (ability == ABILITY_HEATPROOF) { if (burnDamage > (burnDamage / 2) + 1) // Record ability if the burn takes less damage than it normally would. @@ -556,7 +556,7 @@ static bool32 HandleEndTurnFrostbite(u32 battler) && IsBattlerAlive(battler) && !IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MAGIC_GUARD)) { - SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / (B_BURN_DAMAGE >= GEN_7 ? 16 : 8)); + SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / (GetConfig(CONFIG_BURN_DAMAGE) >= GEN_7 ? 16 : 8)); BattleScriptExecute(BattleScript_FrostbiteTurnDmg); effect = TRUE; } diff --git a/test/battle/status1/burn.c b/test/battle/status1/burn.c index 8ec9d05835..33eae7bd85 100644 --- a/test/battle/status1/burn.c +++ b/test/battle/status1/burn.c @@ -7,10 +7,13 @@ ASSUMPTIONS ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); } -SINGLE_BATTLE_TEST("Burn deals 1/16th (Gen7+) or 1/8th damage per turn") +SINGLE_BATTLE_TEST("Burn deals 1/8th damage (Gen1-6) or 1/16th (Gen7+) per turn") { - u32 j; + u32 j, config, value; + PARAMETRIZE { config = GEN_7; value = 16; } + PARAMETRIZE { config = GEN_6; value = 8; } GIVEN { + WITH_CONFIG(CONFIG_BURN_DAMAGE, config); PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -19,7 +22,7 @@ SINGLE_BATTLE_TEST("Burn deals 1/16th (Gen7+) or 1/8th damage per turn") } SCENE { s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); for (j = 0; j < 4; j++) - HP_BAR(player, damage: maxHP / ((B_BURN_DAMAGE >= GEN_7) ? 16 : 8)); + HP_BAR(player, damage: maxHP / value); } } diff --git a/test/battle/status1/frostbite.c b/test/battle/status1/frostbite.c index e221a0eae3..9ae86a8738 100644 --- a/test/battle/status1/frostbite.c +++ b/test/battle/status1/frostbite.c @@ -23,11 +23,14 @@ SINGLE_BATTLE_TEST("Frostbite reduces the special attack by 50 percent") } THEN { EXPECT_EQ(reducedDamage * 2, normaleDamage); } } -SINGLE_BATTLE_TEST("Frostbite deals 1/16th (Gen7+) or 1/8th damage to affected Pokémon") +SINGLE_BATTLE_TEST("Frostbite deals 1/8th damage (Gen1-6) or 1/16th (Gen7+) per turn") { s16 frostbiteDamage; - + u32 config, value; + PARAMETRIZE { config = GEN_7; value = 16; } + PARAMETRIZE { config = GEN_6; value = 8; } GIVEN { + WITH_CONFIG(CONFIG_BURN_DAMAGE, config); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_FROSTBITE); } } WHEN { @@ -36,7 +39,9 @@ SINGLE_BATTLE_TEST("Frostbite deals 1/16th (Gen7+) or 1/8th damage to affected P MESSAGE("The opposing Wobbuffet was hurt by its frostbite!"); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRB, opponent); HP_BAR(opponent, captureDamage: &frostbiteDamage); - } THEN { EXPECT_EQ(frostbiteDamage, opponent->maxHP / ((B_BURN_DAMAGE >= GEN_7) ? 16 : 8)); } + } THEN { + EXPECT_EQ(frostbiteDamage, opponent->maxHP / value); + } } SINGLE_BATTLE_TEST("Frostbite is healed if hit with a thawing move") From 66b666493835e3c9e279e2e5ba7875666cdd47b1 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Fri, 19 Dec 2025 01:26:50 -0300 Subject: [PATCH 09/13] `B_REDIRECT_ABILITY_IMMUNITY` config tests (#8571) --- include/constants/generational_changes.h | 2 +- src/battle_ai_main.c | 8 ++-- src/battle_ai_switch_items.c | 4 +- src/battle_ai_util.c | 2 +- src/battle_script_commands.c | 2 +- src/battle_util.c | 4 +- test/battle/ability/lightning_rod.c | 25 +++++++++--- test/battle/ability/parental_bond.c | 3 +- test/battle/ability/stalwart.c | 14 ++++--- test/battle/ability/storm_drain.c | 14 +++++-- test/battle/ai/ai.c | 1 + test/battle/ai/ai_doubles.c | 50 ++++++------------------ test/battle/ai/ai_switching.c | 4 +- test/battle/gimmick/dynamax.c | 21 +++++----- test/battle/hold_effect/life_orb.c | 1 + test/battle/move_effect/instruct.c | 1 + test/battle/move_effect/pledge.c | 1 + test/battle/move_effect/sleep_talk.c | 2 + test/battle/move_effect/teatime.c | 1 + test/battle/spread_moves.c | 2 + 20 files changed, 85 insertions(+), 77 deletions(-) diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 34a7c14f75..a7a77c944e 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -137,7 +137,7 @@ F(WEATHER_FORMS, weatherForms, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(SYMBIOSIS_GEMS, symbiosisGems, (u32, GEN_COUNT - 1)) \ F(ABSORBING_ABILITY_STRING, absorbingAbilityString, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ - F(REDIRECT_ABILITY_IMMUNITY, redirectAbilityImmunity, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ + F(REDIRECT_ABILITY_IMMUNITY, redirectAbilityImmunity, (u32, GEN_COUNT - 1)) \ F(REDIRECT_ABILITY_ALLIES, redirectAbilityAllies, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(LEAF_GUARD_PREVENTS_REST, leafGuardPreventsRest, (u32, GEN_COUNT - 1)) \ F(TRANSISTOR_BOOST, transistorBoost, (u32, GEN_COUNT - 1)) \ diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index ac97f1e004..795efa9825 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -3398,7 +3398,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case ABILITY_VOLT_ABSORB: if (moveType == TYPE_ELECTRIC) { - if (B_REDIRECT_ABILITY_IMMUNITY < GEN_5 && atkPartnerAbility == ABILITY_LIGHTNING_ROD) + if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) < GEN_5 && atkPartnerAbility == ABILITY_LIGHTNING_ROD) { RETURN_SCORE_MINUS(10); } @@ -3444,7 +3444,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case ABILITY_STORM_DRAIN: if (moveType == TYPE_WATER) { - if (B_REDIRECT_ABILITY_IMMUNITY < GEN_5 && atkPartnerAbility == ABILITY_STORM_DRAIN) + if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) < GEN_5 && atkPartnerAbility == ABILITY_STORM_DRAIN) { RETURN_SCORE_MINUS(10); } @@ -5397,7 +5397,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru case EFFECT_ION_DELUGE: if ((aiData->abilities[battlerAtk] == ABILITY_VOLT_ABSORB || aiData->abilities[battlerAtk] == ABILITY_MOTOR_DRIVE - || (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5 && aiData->abilities[battlerAtk] == ABILITY_LIGHTNING_ROD)) + || (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5 && aiData->abilities[battlerAtk] == ABILITY_LIGHTNING_ROD)) && predictedType == TYPE_NORMAL) ADJUST_SCORE(DECENT_EFFECT); break; @@ -5457,7 +5457,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru if (predictedMove != MOVE_NONE && (aiData->abilities[battlerAtk] == ABILITY_VOLT_ABSORB || aiData->abilities[battlerAtk] == ABILITY_MOTOR_DRIVE - || (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5 && aiData->abilities[battlerAtk] == ABILITY_LIGHTNING_ROD))) + || (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5 && aiData->abilities[battlerAtk] == ABILITY_LIGHTNING_ROD))) { ADJUST_SCORE(DECENT_EFFECT); } diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 31f903c8c9..05091a815e 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -534,14 +534,14 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_WATER_ABSORB; absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_DRY_SKIN; - if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) + if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5) absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_STORM_DRAIN; } else if (incomingType == TYPE_ELECTRIC || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_ELECTRIC)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_VOLT_ABSORB; absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_MOTOR_DRIVE; - if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) + if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5) absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_LIGHTNING_ROD; } else if (incomingType == TYPE_GRASS || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GRASS)) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index a66a24e2e7..3bd77f8e19 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -5766,7 +5766,7 @@ bool32 ShouldTriggerAbility(u32 battlerAtk, u32 battlerDef, enum Ability ability { case ABILITY_LIGHTNING_ROD: case ABILITY_STORM_DRAIN: - if (B_REDIRECT_ABILITY_IMMUNITY < GEN_5) + if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) < GEN_5) return FALSE; else return (BattlerStatCanRise(battlerDef, ability, STAT_SPATK) && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 98eb569ee2..5ba9c2bac5 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -9525,7 +9525,7 @@ static bool32 IsElectricAbilityAffected(u32 battler, enum Ability ability) moveType = GetMoveType(gCurrentMove); if (moveType == TYPE_ELECTRIC - && (ability != ABILITY_LIGHTNING_ROD || B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) + && (ability != ABILITY_LIGHTNING_ROD || GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5) && GetBattlerAbility(battler) == ability) return TRUE; else diff --git a/src/battle_util.c b/src/battle_util.c index 90532e2360..3205529cb3 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3552,14 +3552,14 @@ bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, enum Ability ability } break; case ABILITY_LIGHTNING_ROD: - if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5 && moveType == TYPE_ELECTRIC && GetBattlerMoveTargetType(battlerAtk, move) != MOVE_TARGET_ALL_BATTLERS) + if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5 && moveType == TYPE_ELECTRIC && GetBattlerMoveTargetType(battlerAtk, move) != MOVE_TARGET_ALL_BATTLERS) { effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; statId = STAT_SPATK; } break; case ABILITY_STORM_DRAIN: - if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5 && moveType == TYPE_WATER) + if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5 && moveType == TYPE_WATER) { effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; statId = STAT_SPATK; diff --git a/test/battle/ability/lightning_rod.c b/test/battle/ability/lightning_rod.c index aec2e7fea4..e1fedbe17d 100644 --- a/test/battle/ability/lightning_rod.c +++ b/test/battle/ability/lightning_rod.c @@ -1,16 +1,20 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("Lightning Rod absorbs Electric-type moves and increases the Sp. Attack [Gen5+]") +SINGLE_BATTLE_TEST("Lightning Rod absorbs Electric-type moves and increases the Sp. Attack (Gen5+)") { + u32 config; + PARAMETRIZE { config = GEN_4; } + PARAMETRIZE { config = GEN_5; } GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, config); ASSUME(GetMoveType(MOVE_THUNDERBOLT) == TYPE_ELECTRIC); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_LIGHTNING_ROD); } } WHEN { TURN { MOVE(player, MOVE_THUNDERBOLT); MOVE(opponent, MOVE_CELEBRATE); } } SCENE { - if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) { + if (config >= GEN_5) { NONE_OF { ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player); HP_BAR(opponent); @@ -29,13 +33,21 @@ SINGLE_BATTLE_TEST("Lightning Rod absorbs Electric-type moves and increases the } ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); } THEN { - EXPECT_EQ(gBattleHistory->abilities[1], ABILITY_LIGHTNING_ROD); // Check if the correct ability has been recorded + if (config >= GEN_5) { + EXPECT_EQ(gBattleHistory->abilities[1], ABILITY_LIGHTNING_ROD); // Check if the correct ability has been recorded + } else { + EXPECT_EQ(gBattleHistory->abilities[1], ABILITY_NONE); + } } } DOUBLE_BATTLE_TEST("Lightning Rod forces single-target Electric-type moves to target the Pokémon with this Ability.") { + u32 config; + PARAMETRIZE { config = GEN_4; } + PARAMETRIZE { config = GEN_5; } GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, config); ASSUME(GetMoveType(MOVE_THUNDERBOLT) == TYPE_ELECTRIC); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); @@ -49,7 +61,7 @@ DOUBLE_BATTLE_TEST("Lightning Rod forces single-target Electric-type moves to ta MOVE(opponentRight, MOVE_CELEBRATE); } } SCENE { - if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) { + if (config >= GEN_5) { NONE_OF { HP_BAR(opponentLeft); HP_BAR(opponentRight); @@ -86,7 +98,7 @@ DOUBLE_BATTLE_TEST("Lightning Rod redirects an ally's attack") TURN { MOVE(opponentRight, MOVE_THUNDERBOLT, target: playerLeft); } } SCENE { MESSAGE("The opposing Wobbuffet used Thunderbolt!"); - if (B_REDIRECT_ABILITY_ALLIES >= GEN_5) + if (B_REDIRECT_ABILITY_ALLIES >= GEN_4) { NOT HP_BAR(playerLeft); ABILITY_POPUP(opponentLeft, ABILITY_LIGHTNING_ROD); @@ -100,9 +112,10 @@ DOUBLE_BATTLE_TEST("Lightning Rod redirects an ally's attack") } } -DOUBLE_BATTLE_TEST("Lightning Rod absorbs moves that targets all battlers but does not redirect") +DOUBLE_BATTLE_TEST("Lightning Rod absorbs moves that targets all battlers but does not redirect (Gen6+)") { GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); diff --git a/test/battle/ability/parental_bond.c b/test/battle/ability/parental_bond.c index 824399379c..d8be7c76f2 100644 --- a/test/battle/ability/parental_bond.c +++ b/test/battle/ability/parental_bond.c @@ -109,8 +109,9 @@ SINGLE_BATTLE_TEST("Parental Bond-converted moves only hit once on Lightning Rod enum Type type; enum Ability ability; PARAMETRIZE { move = MOVE_THUNDERBOLT; ability = ABILITY_LIGHTNING_ROD; species = SPECIES_RAICHU; type = TYPE_ELECTRIC; } - PARAMETRIZE { move = MOVE_SURF; ability = ABILITY_STORM_DRAIN; species = SPECIES_LILEEP; type = TYPE_WATER; } + PARAMETRIZE { move = MOVE_SURF; ability = ABILITY_STORM_DRAIN; species = SPECIES_LILEEP; type = TYPE_WATER; } GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); ASSUME(GetMoveStrikeCount(move) < 2); ASSUME(GetMoveType(move) == type); PLAYER(SPECIES_KANGASKHAN) { Item(ITEM_KANGASKHANITE); } diff --git a/test/battle/ability/stalwart.c b/test/battle/ability/stalwart.c index 6bebe17cb6..3cca8e58ad 100644 --- a/test/battle/ability/stalwart.c +++ b/test/battle/ability/stalwart.c @@ -21,10 +21,13 @@ DOUBLE_BATTLE_TEST("Stalwart ignores redirection from Follow-Me") DOUBLE_BATTLE_TEST("Stalwart stops Lightning Rod and Storm Drain from redirecting moves") { enum Ability ability; - u32 species; - PARAMETRIZE { ability = ABILITY_STORM_DRAIN; species = SPECIES_LUMINEON; } - PARAMETRIZE { ability = ABILITY_LIGHTNING_ROD; species = SPECIES_RAICHU; } + u32 species, config; + PARAMETRIZE { ability = ABILITY_STORM_DRAIN; species = SPECIES_LUMINEON; config = GEN_4; } + PARAMETRIZE { ability = ABILITY_STORM_DRAIN; species = SPECIES_LUMINEON; config = GEN_5; } + PARAMETRIZE { ability = ABILITY_LIGHTNING_ROD; species = SPECIES_RAICHU; config = GEN_4; } + PARAMETRIZE { ability = ABILITY_LIGHTNING_ROD; species = SPECIES_RAICHU; config = GEN_5; } GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, config); ASSUME(GetMoveType(MOVE_SPARK) == TYPE_ELECTRIC); ASSUME(GetMoveType(MOVE_WATER_GUN) == TYPE_WATER); PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_STALWART); } @@ -39,15 +42,14 @@ DOUBLE_BATTLE_TEST("Stalwart stops Lightning Rod and Storm Drain from redirectin MOVE(playerLeft, MOVE_WATER_GUN, target: opponentRight); } } SCENE { - if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) { - HP_BAR(opponentRight); + HP_BAR(opponentRight); + if (config >= GEN_5) { NONE_OF { ABILITY_POPUP(opponentLeft, ability); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); MESSAGE("The opposing Raichu's Sp. Atk rose!"); } } else { - HP_BAR(opponentRight); NONE_OF { HP_BAR(opponentLeft); } diff --git a/test/battle/ability/storm_drain.c b/test/battle/ability/storm_drain.c index 962317b108..c1cf5c890b 100644 --- a/test/battle/ability/storm_drain.c +++ b/test/battle/ability/storm_drain.c @@ -1,16 +1,20 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("Storm Drain absorbs Water-type moves and increases the Sp. Attack [Gen5+]") +SINGLE_BATTLE_TEST("Storm Drain absorbs Water-type moves and increases the Sp. Attack (Gen5+)") { + u32 config; + PARAMETRIZE { config = GEN_4; } + PARAMETRIZE { config = GEN_5; } GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, config); ASSUME(GetMoveType(MOVE_WATER_GUN) == TYPE_WATER); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_GASTRODON_EAST) { Ability(ABILITY_STORM_DRAIN); } } WHEN { TURN { MOVE(player, MOVE_WATER_GUN); MOVE(opponent, MOVE_CELEBRATE); } } SCENE { - if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) { + if (config >= GEN_5) { NONE_OF { ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, player); HP_BAR(opponent); @@ -33,7 +37,11 @@ SINGLE_BATTLE_TEST("Storm Drain absorbs Water-type moves and increases the Sp. A DOUBLE_BATTLE_TEST("Storm Drain forces single-target Water-type moves to target the Pokémon with this Ability.") { + u32 config; + PARAMETRIZE { config = GEN_4; } + PARAMETRIZE { config = GEN_5; } GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, config); ASSUME(GetMoveType(MOVE_WATER_GUN) == TYPE_WATER); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); @@ -47,7 +55,7 @@ DOUBLE_BATTLE_TEST("Storm Drain forces single-target Water-type moves to target MOVE(opponentRight, MOVE_CELEBRATE); } } SCENE { - if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) { + if (config >= GEN_5) { NONE_OF { HP_BAR(opponentLeft); HP_BAR(opponentRight); diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index 61100a9161..0cb4a97375 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -846,6 +846,7 @@ AI_DOUBLE_BATTLE_TEST("AI sees opposing drain ability") ASSUME(GetMoveType(MOVE_THUNDERBOLT) == TYPE_ELECTRIC); ASSUME(GetMoveType(MOVE_RAZOR_LEAF) != TYPE_ELECTRIC); ASSUME(GetMoveType(MOVE_METAL_CLAW) != TYPE_ELECTRIC); + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); PLAYER(SPECIES_RAICHU) { Ability(ABILITY_LIGHTNING_ROD); Moves(MOVE_CELEBRATE); } PLAYER(SPECIES_KRABBY) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_CELEBRATE); } diff --git a/test/battle/ai/ai_doubles.c b/test/battle/ai/ai_doubles.c index a35ff248bd..746789cf5e 100644 --- a/test/battle/ai/ai_doubles.c +++ b/test/battle/ai/ai_doubles.c @@ -542,54 +542,28 @@ AI_DOUBLE_BATTLE_TEST("AI sees corresponding absorbing abilities on partners") } } -AI_DOUBLE_BATTLE_TEST("AI treats an ally's redirection ability appropriately (gen 4)") +AI_DOUBLE_BATTLE_TEST("AI treats an ally's redirection ability appropriately") { - KNOWN_FAILING; - ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); - ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); - ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY); - ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); + u32 ability, move, species, config, expectedMove; - enum Ability ability; - u32 move, species; - - PARAMETRIZE { species = SPECIES_SEAKING; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; } - PARAMETRIZE { species = SPECIES_SHELLOS; ability = ABILITY_STORM_DRAIN; move = MOVE_SURF; } + PARAMETRIZE { species = SPECIES_SEAKING; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; config = GEN_4; expectedMove = MOVE_HEADBUTT; } + PARAMETRIZE { species = SPECIES_SHELLOS; ability = ABILITY_STORM_DRAIN; move = MOVE_SURF; config = GEN_4; expectedMove = MOVE_HEADBUTT; } + PARAMETRIZE { species = SPECIES_SEAKING; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; config = GEN_5; expectedMove = MOVE_DISCHARGE; } + PARAMETRIZE { species = SPECIES_SHELLOS; ability = ABILITY_STORM_DRAIN; move = MOVE_SURF; config = GEN_5; expectedMove = MOVE_SURF; } GIVEN { + ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); + ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE); - WITH_CONFIG(B_REDIRECT_ABILITY_IMMUNITY, GEN_4); + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, config); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Moves(move, MOVE_HEADBUTT); } OPPONENT(species) { HP(1); Ability(ability); Moves(MOVE_ROUND); } } WHEN { - TURN { EXPECT_MOVE(opponentLeft, MOVE_HEADBUTT); } - } -} - -AI_DOUBLE_BATTLE_TEST("AI treats an ally's redirection ability appropriately (gen 5+)") -{ - ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); - ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); - ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY); - ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); - - enum Ability ability; - u32 move, species; - - PARAMETRIZE { species = SPECIES_SEAKING; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; } - PARAMETRIZE { species = SPECIES_SHELLOS; ability = ABILITY_STORM_DRAIN; move = MOVE_SURF; } - - GIVEN { - AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE); - WITH_CONFIG(B_REDIRECT_ABILITY_IMMUNITY, GEN_5); - PLAYER(SPECIES_WOBBUFFET); - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET) { Moves(move, MOVE_HEADBUTT); } - OPPONENT(species) { HP(1); Ability(ability); Moves(MOVE_ROUND); } - } WHEN { - TURN { EXPECT_MOVE(opponentLeft, move); } + TURN { EXPECT_MOVE(opponentLeft, expectedMove); } } } diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 8c6c6ae623..91098f28a3 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -1109,7 +1109,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has an PARAMETRIZE { aiMon = SPECIES_CHESNAUGHT; absorbingAbility = ABILITY_BULLETPROOF; move = MOVE_SLUDGE_BOMB;} PARAMETRIZE { aiMon = SPECIES_BRAMBLEGHAST; absorbingAbility = ABILITY_WIND_RIDER; move = MOVE_HURRICANE;} GIVEN { - ASSUME(B_REDIRECT_ABILITY_IMMUNITY >= GEN_5); + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); 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); } OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } @@ -1662,7 +1662,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will consider Hidden Power wh { PASSES_RANDOMLY(SHOULD_SWITCH_ABSORBS_HIDDEN_POWER_PERCENTAGE, 100, RNG_AI_SWITCH_ABSORBING_HIDDEN_POWER); GIVEN { - ASSUME(B_REDIRECT_ABILITY_IMMUNITY >= GEN_5); + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); 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_HIDDEN_POWER); HPIV(31); AttackIV(30); DefenseIV(31); SpAttackIV(30); SpDefenseIV(31); SpeedIV(30); } OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } diff --git a/test/battle/gimmick/dynamax.c b/test/battle/gimmick/dynamax.c index bf4abb86d5..25a0d5d08e 100644 --- a/test/battle/gimmick/dynamax.c +++ b/test/battle/gimmick/dynamax.c @@ -1611,16 +1611,16 @@ SINGLE_BATTLE_TEST("Dynamax: Max Moves don't bypass absorbing abilities") { u32 move, species; enum Ability ability; - PARAMETRIZE { move = MOVE_SPARK; ability = ABILITY_VOLT_ABSORB; species = SPECIES_LANTURN; } - PARAMETRIZE { move = MOVE_WATER_GUN; ability = ABILITY_WATER_ABSORB; species = SPECIES_LANTURN; } - PARAMETRIZE { move = MOVE_EMBER; ability = ABILITY_FLASH_FIRE; species = SPECIES_HEATRAN; } - PARAMETRIZE { move = MOVE_SPARK; ability = ABILITY_LIGHTNING_ROD; species = SPECIES_PIKACHU; } - PARAMETRIZE { move = MOVE_WATER_GUN; ability = ABILITY_STORM_DRAIN; species = SPECIES_GASTRODON; } - PARAMETRIZE { move = MOVE_EMBER; ability = ABILITY_WELL_BAKED_BODY; species = SPECIES_DACHSBUN; } - PARAMETRIZE { move = MOVE_SPARK; ability = ABILITY_MOTOR_DRIVE; species = SPECIES_ELECTIVIRE; } - PARAMETRIZE { move = MOVE_WATER_GUN; ability = ABILITY_DRY_SKIN; species = SPECIES_PARASECT; } - PARAMETRIZE { move = MOVE_MUD_BOMB; ability = ABILITY_EARTH_EATER; species = SPECIES_ORTHWORM; } - PARAMETRIZE { move = MOVE_VINE_WHIP; ability = ABILITY_SAP_SIPPER; species = SPECIES_MILTANK; } + PARAMETRIZE { move = MOVE_SPARK; ability = ABILITY_VOLT_ABSORB; species = SPECIES_LANTURN; } + PARAMETRIZE { move = MOVE_WATER_GUN; ability = ABILITY_WATER_ABSORB; species = SPECIES_LANTURN; } + PARAMETRIZE { move = MOVE_EMBER; ability = ABILITY_FLASH_FIRE; species = SPECIES_HEATRAN; } + PARAMETRIZE { move = MOVE_SPARK; ability = ABILITY_LIGHTNING_ROD; species = SPECIES_PIKACHU; } + PARAMETRIZE { move = MOVE_WATER_GUN; ability = ABILITY_STORM_DRAIN; species = SPECIES_GASTRODON; } + PARAMETRIZE { move = MOVE_EMBER; ability = ABILITY_WELL_BAKED_BODY; species = SPECIES_DACHSBUN; } + PARAMETRIZE { move = MOVE_SPARK; ability = ABILITY_MOTOR_DRIVE; species = SPECIES_ELECTIVIRE; } + PARAMETRIZE { move = MOVE_WATER_GUN; ability = ABILITY_DRY_SKIN; species = SPECIES_PARASECT; } + PARAMETRIZE { move = MOVE_MUD_BOMB; ability = ABILITY_EARTH_EATER; species = SPECIES_ORTHWORM; } + PARAMETRIZE { move = MOVE_VINE_WHIP; ability = ABILITY_SAP_SIPPER; species = SPECIES_MILTANK; } GIVEN { ASSUME(GetMoveType(MOVE_WATER_GUN) == TYPE_WATER); @@ -1628,6 +1628,7 @@ SINGLE_BATTLE_TEST("Dynamax: Max Moves don't bypass absorbing abilities") ASSUME(GetMoveType(MOVE_EMBER) == TYPE_FIRE); ASSUME(GetMoveType(MOVE_MUD_BOMB) == TYPE_GROUND); ASSUME(GetMoveType(MOVE_VINE_WHIP) == TYPE_GRASS); + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); PLAYER(SPECIES_WOBBUFFET); OPPONENT(species) { Ability(ability); } } WHEN { diff --git a/test/battle/hold_effect/life_orb.c b/test/battle/hold_effect/life_orb.c index 7d99449bcb..39891ca53b 100644 --- a/test/battle/hold_effect/life_orb.c +++ b/test/battle/hold_effect/life_orb.c @@ -110,6 +110,7 @@ SINGLE_BATTLE_TEST("Life Orb does not activate if on a confusion hit") SINGLE_BATTLE_TEST("Life Orb does not activate if move was absorbed by target") { GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); } OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_LIGHTNING_ROD); } } WHEN { diff --git a/test/battle/move_effect/instruct.c b/test/battle/move_effect/instruct.c index 834546009f..be6e557990 100644 --- a/test/battle/move_effect/instruct.c +++ b/test/battle/move_effect/instruct.c @@ -239,6 +239,7 @@ DOUBLE_BATTLE_TEST("Instructed move will be redirected and absorbed by Lightning PARAMETRIZE { moveTarget = opponentLeft; } PARAMETRIZE { moveTarget = opponentRight; } GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WYNAUT); OPPONENT(SPECIES_PIKACHU) { Ability(ABILITY_LIGHTNING_ROD); } diff --git a/test/battle/move_effect/pledge.c b/test/battle/move_effect/pledge.c index a7935450b6..f2607b4ff7 100644 --- a/test/battle/move_effect/pledge.c +++ b/test/battle/move_effect/pledge.c @@ -910,6 +910,7 @@ DOUBLE_BATTLE_TEST("Pledge move combo doesn't trigger on opponent's Pledge move DOUBLE_BATTLE_TEST("Pledge move combo doesn't trigger on opponent's Pledge move - Storm Drain") { GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); PLAYER(SPECIES_GASTRODON) { Ability(ABILITY_STORM_DRAIN); } PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); diff --git a/test/battle/move_effect/sleep_talk.c b/test/battle/move_effect/sleep_talk.c index ace40150c5..6980954714 100644 --- a/test/battle/move_effect/sleep_talk.c +++ b/test/battle/move_effect/sleep_talk.c @@ -113,6 +113,7 @@ DOUBLE_BATTLE_TEST("Sleep Talk calls move and that move may be redirected by Lig { PASSES_RANDOMLY(1, 2, RNG_RANDOM_TARGET); GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); ASSUME(GetMoveType(MOVE_SPARK) == TYPE_ELECTRIC); PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_SPARK, MOVE_FLY, MOVE_DIG); } PLAYER(SPECIES_WOBBUFFET); @@ -132,6 +133,7 @@ DOUBLE_BATTLE_TEST("Sleep Talk calls move and that move may be redirected by Sto { PASSES_RANDOMLY(1, 2, RNG_RANDOM_TARGET); GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); ASSUME(GetMoveType(MOVE_WATER_GUN) == TYPE_WATER); PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_WATER_GUN, MOVE_FLY, MOVE_DIG); } PLAYER(SPECIES_WOBBUFFET); diff --git a/test/battle/move_effect/teatime.c b/test/battle/move_effect/teatime.c index fc4ad22198..b5935d0cde 100644 --- a/test/battle/move_effect/teatime.c +++ b/test/battle/move_effect/teatime.c @@ -205,6 +205,7 @@ SINGLE_BATTLE_TEST("Teatime triggers Lightning Rod if it has been affected by El PARAMETRIZE { move = MOVE_PLASMA_FISTS; item = ITEM_NONE; } GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); PLAYER(SPECIES_PIKACHU) { Ability(ABILITY_LIGHTNING_ROD); Item(item); } OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LIECHI_BERRY); } } WHEN { diff --git a/test/battle/spread_moves.c b/test/battle/spread_moves.c index d5e6b5c26d..68b9f40273 100644 --- a/test/battle/spread_moves.c +++ b/test/battle/spread_moves.c @@ -172,6 +172,7 @@ DOUBLE_BATTLE_TEST("Spread Moves: A spread move attack will be weakened by stron DOUBLE_BATTLE_TEST("Spread Moves: AOE move vs Disguise, Volt Absorb (right) and Lightning Rod (left)") { GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); PLAYER(SPECIES_WOBBUFFET); @@ -191,6 +192,7 @@ DOUBLE_BATTLE_TEST("Spread Moves: AOE move vs Disguise, Volt Absorb (right) and DOUBLE_BATTLE_TEST("Spread Moves: AOE move vs Disguise, Volt Absorb (left) and Lightning Rod (right)") { GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); PLAYER(SPECIES_WOBBUFFET); From cc6ac23185d38fc19bfdb11a3101617e7be96612 Mon Sep 17 00:00:00 2001 From: surskitty Date: Thu, 18 Dec 2025 23:57:03 -0500 Subject: [PATCH 10/13] Fixing crash from Last Resort with Normalium Z in certain damage thresholds (#8583) --- src/battle_ai_util.c | 3 +++ test/battle/ai/gimmick_z_move.c | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 3bd77f8e19..a9c4b61284 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -5241,6 +5241,9 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove) return FALSE; } + if (GetMoveEffect(chosenMove) == EFFECT_LAST_RESORT && !CanUseLastResort(battlerAtk)) + return TRUE; + uq4_12_t effectiveness; struct SimulatedDamage dmg; diff --git a/test/battle/ai/gimmick_z_move.c b/test/battle/ai/gimmick_z_move.c index fcc8d81658..b6432987b7 100644 --- a/test/battle/ai/gimmick_z_move.c +++ b/test/battle/ai/gimmick_z_move.c @@ -47,6 +47,18 @@ AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Extreme Evoboost") } } +AI_SINGLE_BATTLE_TEST("AI uses Z-Moves to bypass move limitations") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT ); + ASSUME(GetMoveType(MOVE_QUICK_ATTACK) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); Moves(MOVE_POUND, MOVE_LAST_RESORT); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_LAST_RESORT, gimmick: GIMMICK_Z_MOVE); } + } +} + AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- 10,000,000 Volt Thunderbolt") { GIVEN { From 9451af1c64d058e53f9584379912b03a067a331b Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Fri, 19 Dec 2025 12:17:24 -0300 Subject: [PATCH 11/13] `B_UPDATED_CONVERSION_2` config tests (#8573) --- docs/tutorials/how_to_testing_system.md | 20 +-- include/constants/generational_changes.h | 2 +- src/battle_script_commands.c | 161 ++++++++--------------- test/battle/gimmick/terastal.c | 47 ------- test/battle/move_effect/conversion.c | 13 ++ test/battle/move_effect/conversion_2.c | 93 +++++++++---- 6 files changed, 147 insertions(+), 189 deletions(-) diff --git a/docs/tutorials/how_to_testing_system.md b/docs/tutorials/how_to_testing_system.md index c2b9912898..70ee6c4033 100644 --- a/docs/tutorials/how_to_testing_system.md +++ b/docs/tutorials/how_to_testing_system.md @@ -497,10 +497,10 @@ Causes the test to fail if the `SCENE` command succeeds before the following com ``` Causes the test to fail unless one of the `SCENE` commands succeeds. ``` - ONE_OF { - MESSAGE("Wobbuffet used Celebrate!"); - MESSAGE("Wobbuffet couldn't move because it's paralyzed!"); - } + ONE_OF { + MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Wobbuffet couldn't move because it's paralyzed!"); + } ``` ### `NONE_OF` @@ -511,12 +511,12 @@ Causes the test to fail unless one of the `SCENE` commands succeeds. ``` Causes the test to fail if one of the `SCENE` commands succeeds before the command after the `NONE_OF` succeeds. ``` - // Our Wobbuffet does not move before the foe's. - NONE_OF { - MESSAGE("Wobbuffet used Celebrate!"); - MESSAGE("Wobbuffet couldn't move because it's paralyzed!"); - } - MESSAGE("The opposing Wobbuffet used Celebrate!"); + // Our Wobbuffet does not move before the foe's. + NONE_OF { + MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Wobbuffet couldn't move because it's paralyzed!"); + } + MESSAGE("The opposing Wobbuffet used Celebrate!"); ``` ### `PLAYER_PARTY` diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index a7a77c944e..ecbdd8a95e 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -66,7 +66,7 @@ F(RECOIL_IF_MISS_DMG, recoilIfMissDmg, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(KLUTZ_FLING_INTERACTION, klutzFlingInteraction, (u32, GEN_COUNT - 1)) \ F(UPDATED_CONVERSION, updatedConversion, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ - F(UPDATED_CONVERSION_2, updatedConversion2, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ + F(UPDATED_CONVERSION_2, updatedConversion2, (u32, GEN_COUNT - 1)) \ F(PP_REDUCED_BY_SPITE, ppReducedBySpite, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(EXTRAPOLATED_MOVE_FLAGS, extrapolatedMoveFlags, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ /* Ability data settings */ \ diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 5ba9c2bac5..516e817be5 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -11526,120 +11526,75 @@ static void Cmd_settypetorandomresistance(void) // Before Gen 5 Conversion 2 only worked on a move the attacker was actually hit by. // This changed later to the last move used by the selected target. - if (B_UPDATED_CONVERSION_2 < GEN_5) + u32 moveToCheck; + u32 typeToCheck; + + if (GetConfig(CONFIG_UPDATED_CONVERSION_2) < GEN_5) { - if (gLastLandedMoves[gBattlerAttacker] == MOVE_NONE - || gLastLandedMoves[gBattlerAttacker] == MOVE_UNAVAILABLE) - { - gBattlescriptCurrInstr = cmd->failInstr; - } - else if (gBattleMoveEffects[GetMoveEffect(gLastLandedMoves[gBattlerAttacker])].twoTurnEffect - && gBattleMons[gLastHitBy[gBattlerAttacker]].volatiles.multipleTurns) - { - gBattlescriptCurrInstr = cmd->failInstr; - } - else if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA) - { - gBattlescriptCurrInstr = cmd->failInstr; - } - else if (gLastHitByType[gBattlerAttacker] == TYPE_STELLAR || gLastHitByType[gBattlerAttacker] == TYPE_MYSTERY) - { - gBattlescriptCurrInstr = cmd->failInstr; - } + moveToCheck = gLastLandedMoves[gBattlerAttacker]; + if (GetMoveEffect(moveToCheck) == EFFECT_STRUGGLE) + typeToCheck = TYPE_NORMAL; else - { - u32 i, resistTypes = 0; - u32 hitByType = gLastHitByType[gBattlerAttacker]; - - for (i = 0; i < NUMBER_OF_MON_TYPES; i++) // Find all types that resist. - { - switch (GetTypeModifier(hitByType, i)) - { - case UQ_4_12(0): - case UQ_4_12(0.5): - resistTypes |= 1u << i; - break; - } - } - - while (resistTypes != 0) - { - i = Random() % NUMBER_OF_MON_TYPES; - if (resistTypes & 1u << i) - { - if (IS_BATTLER_OF_TYPE(gBattlerAttacker, i)) - { - resistTypes &= ~(1u << i); // Type resists, but the user is already of this type. - } - else - { - SET_BATTLER_TYPE(gBattlerAttacker, i); - PREPARE_TYPE_BUFFER(gBattleTextBuff1, i); - gBattlescriptCurrInstr = cmd->nextInstr; - return; - } - } - } - - gBattlescriptCurrInstr = cmd->failInstr; - } + typeToCheck = gLastHitByType[gBattlerAttacker]; } else { - if (gLastResultingMoves[gBattlerTarget] == MOVE_NONE - || gLastResultingMoves[gBattlerTarget] == MOVE_UNAVAILABLE - || gLastResultingMoves[gBattlerTarget] == MOVE_STRUGGLE) - { - gBattlescriptCurrInstr = cmd->failInstr; - } - else if (!BreaksThroughSemiInvulnerablity(gBattlerTarget, gCurrentMove)) - { - gBattlescriptCurrInstr = cmd->failInstr; - } - else if (gLastUsedMoveType[gBattlerTarget] == TYPE_NONE || gLastUsedMoveType[gBattlerTarget] == TYPE_STELLAR || gLastUsedMoveType[gBattlerTarget] == TYPE_MYSTERY) - { - gBattlescriptCurrInstr = cmd->failInstr; - } - else if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA) - { - gBattlescriptCurrInstr = cmd->failInstr; - } - else - { - u32 i, resistTypes = 0; + moveToCheck = gLastResultingMoves[gBattlerTarget]; + typeToCheck = gLastUsedMoveType[gBattlerTarget]; + } - for (i = 0; i < NUMBER_OF_MON_TYPES; i++) // Find all types that resist. + if (moveToCheck == MOVE_NONE + || moveToCheck == MOVE_UNAVAILABLE) + { + gBattlescriptCurrInstr = cmd->failInstr; + } + else if (!BreaksThroughSemiInvulnerablity(gBattlerTarget, moveToCheck)) + { + gBattlescriptCurrInstr = cmd->failInstr; + } + else if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA) + { + gBattlescriptCurrInstr = cmd->failInstr; + } + else if (typeToCheck == TYPE_NONE || typeToCheck == TYPE_STELLAR || typeToCheck == TYPE_MYSTERY) + { + gBattlescriptCurrInstr = cmd->failInstr; + } + else + { + u32 i, resistTypes = 0; + + for (i = 0; i < NUMBER_OF_MON_TYPES; i++) // Find all types that resist. + { + switch (GetTypeModifier(typeToCheck, i)) { - switch (GetTypeModifier(gLastUsedMoveType[gBattlerTarget], i)) + case UQ_4_12(0): + case UQ_4_12(0.5): + resistTypes |= 1u << i; + break; + } + } + + while (resistTypes != 0) + { + i = Random() % NUMBER_OF_MON_TYPES; + if (resistTypes & 1u << i) + { + if (IS_BATTLER_OF_TYPE(gBattlerAttacker, i)) { - case UQ_4_12(0): - case UQ_4_12(0.5): - resistTypes |= 1u << i; - break; + resistTypes &= ~(1u << i); // Type resists, but the user is already of this type. + } + else + { + SET_BATTLER_TYPE(gBattlerAttacker, i); + PREPARE_TYPE_BUFFER(gBattleTextBuff1, i); + gBattlescriptCurrInstr = cmd->nextInstr; + return; } } - - while (resistTypes != 0) - { - i = Random() % NUMBER_OF_MON_TYPES; - if (resistTypes & 1u << i) - { - if (IS_BATTLER_OF_TYPE(gBattlerAttacker, i)) - { - resistTypes &= ~(1u << i); // Type resists, but the user is already of this type. - } - else - { - SET_BATTLER_TYPE(gBattlerAttacker, i); - PREPARE_TYPE_BUFFER(gBattleTextBuff1, i); - gBattlescriptCurrInstr = cmd->nextInstr; - return; - } - } - } - - gBattlescriptCurrInstr = cmd->failInstr; } + + gBattlescriptCurrInstr = cmd->failInstr; } } diff --git a/test/battle/gimmick/terastal.c b/test/battle/gimmick/terastal.c index d78a68dc53..2379df58dc 100644 --- a/test/battle/gimmick/terastal.c +++ b/test/battle/gimmick/terastal.c @@ -327,33 +327,6 @@ SINGLE_BATTLE_TEST("(TERA) Reflect Type fails if used by a Terastallized Pokemon } } -SINGLE_BATTLE_TEST("(TERA) Conversion fails if used by a Terastallized Pokemon") -{ - GIVEN { - PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(player, MOVE_CONVERSION, gimmick: GIMMICK_TERA); } - } SCENE { - MESSAGE("Wobbuffet used Conversion!"); - MESSAGE("But it failed!"); - } -} - -SINGLE_BATTLE_TEST("(TERA) Conversion2 fails if used by a Terastallized Pokemon") -{ - GIVEN { - PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(opponent, MOVE_SCRATCH); } - TURN { MOVE(player, MOVE_CONVERSION_2, gimmick: GIMMICK_TERA); } - } SCENE { - MESSAGE("Wobbuffet used Conversion 2!"); - MESSAGE("But it failed!"); - } -} - SINGLE_BATTLE_TEST("(TERA) Reflect Type copies a Terastallized Pokemon's Tera Type") { GIVEN { @@ -506,26 +479,6 @@ SINGLE_BATTLE_TEST("(TERA) Revelation Dance uses a Stellar-type Pokemon's base t } } -#if B_UPDATED_CONVERSION_2 < GEN_5 -SINGLE_BATTLE_TEST("(TERA) Conversion2 fails if last hit by a Stellar-type move") -{ - GIVEN { - PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); } - TURN { MOVE(opponent, MOVE_CONVERSION_2); } - } SCENE { - // turn 1 - MESSAGE("Wobbuffet used Tera Blast!"); - ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); - // turn 2 - MESSAGE("The opposing Wobbuffet used Conversion 2!"); - MESSAGE("But it failed!"); - } -} -#endif - SINGLE_BATTLE_TEST("(TERA) Roost does not remove Flying-type ground immunity when Terastallized into the Stellar type") { GIVEN { diff --git a/test/battle/move_effect/conversion.c b/test/battle/move_effect/conversion.c index ac863cf2b2..165d274a50 100644 --- a/test/battle/move_effect/conversion.c +++ b/test/battle/move_effect/conversion.c @@ -8,3 +8,16 @@ TO_DO_BATTLE_TEST("Conversion fails if all the user's moves share types with the TO_DO_BATTLE_TEST("Conversion changes the user's types to the one in the user's first slot (Gen 6+)"); TO_DO_BATTLE_TEST("Conversion can read the user's first move slot even if that move cannot be selected (Gen 6+)"); //Eg. Disable TO_DO_BATTLE_TEST("Conversion can change the user's types to Conversion's type"); + +SINGLE_BATTLE_TEST("(TERA) Conversion fails if used by a Terastallized Pokemon") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CONVERSION, gimmick: GIMMICK_TERA); } + } SCENE { + MESSAGE("Wobbuffet used Conversion!"); + MESSAGE("But it failed!"); + } +} diff --git a/test/battle/move_effect/conversion_2.c b/test/battle/move_effect/conversion_2.c index acd21125c3..169243019b 100644 --- a/test/battle/move_effect/conversion_2.c +++ b/test/battle/move_effect/conversion_2.c @@ -3,10 +3,10 @@ TO_DO_BATTLE_TEST("Conversion 2's type change considers Inverse Battles"); -#if B_UPDATED_CONVERSION_2 < GEN_5 -SINGLE_BATTLE_TEST("Conversion 2 randomly changes the type of the user to a type that resists the last move that hit the user (Gen 3-4)") +SINGLE_BATTLE_TEST("Conversion 2 randomly changes the type of the user to a type that resists the last move that hit the user (Gen 1-4)") { GIVEN { + WITH_CONFIG(CONFIG_UPDATED_CONVERSION_2, GEN_4); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -16,15 +16,16 @@ SINGLE_BATTLE_TEST("Conversion 2 randomly changes the type of the user to a type MESSAGE("Wobbuffet used Ominous Wind!"); // turn 1 ONE_OF { - MESSAGE("The opposing Wobbuffet transformed into the Normal type!"); - MESSAGE("The opposing Wobbuffet transformed into the Dark type!"); + MESSAGE("The opposing Wobbuffet transformed into the Normal type!"); + MESSAGE("The opposing Wobbuffet transformed into the Dark type!"); } } } -SINGLE_BATTLE_TEST("Conversion 2's type change considers Struggle to be Normal type (Gen 3-4)") +SINGLE_BATTLE_TEST("Conversion 2's type change considers Struggle to be Normal type (Gen 1-4)") { GIVEN { + WITH_CONFIG(CONFIG_UPDATED_CONVERSION_2, GEN_4); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -35,18 +36,17 @@ SINGLE_BATTLE_TEST("Conversion 2's type change considers Struggle to be Normal t MESSAGE("The opposing Wobbuffet used Struggle!"); // turn 2 ONE_OF { - MESSAGE("Wobbuffet transformed into the Steel type!"); - MESSAGE("Wobbuffet transformed into the Rock type!"); - MESSAGE("Wobbuffet transformed into the Ghost type!"); + MESSAGE("Wobbuffet transformed into the Steel type!"); + MESSAGE("Wobbuffet transformed into the Rock type!"); + MESSAGE("Wobbuffet transformed into the Ghost type!"); } } } -#endif -#if B_UPDATED_CONVERSION_2 >= GEN_5 SINGLE_BATTLE_TEST("Conversion 2 randomly changes the type of the user to a type that resists the last used target's move (Gen 5+)") { GIVEN { + WITH_CONFIG(CONFIG_UPDATED_CONVERSION_2, GEN_5); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -56,8 +56,8 @@ SINGLE_BATTLE_TEST("Conversion 2 randomly changes the type of the user to a type MESSAGE("Wobbuffet used Ominous Wind!"); // turn 1 ONE_OF { - MESSAGE("The opposing Wobbuffet transformed into the Normal type!"); - MESSAGE("The opposing Wobbuffet transformed into the Dark type!"); + MESSAGE("The opposing Wobbuffet transformed into the Normal type!"); + MESSAGE("The opposing Wobbuffet transformed into the Dark type!"); } } } @@ -65,6 +65,7 @@ SINGLE_BATTLE_TEST("Conversion 2 randomly changes the type of the user to a type SINGLE_BATTLE_TEST("Conversion 2's type change considers status moves (Gen 5+)") { GIVEN { + WITH_CONFIG(CONFIG_UPDATED_CONVERSION_2, GEN_5); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -75,8 +76,8 @@ SINGLE_BATTLE_TEST("Conversion 2's type change considers status moves (Gen 5+)") MESSAGE("The opposing Wobbuffet used Curse!"); // turn 2 ONE_OF { - MESSAGE("Wobbuffet transformed into the Normal type!"); - MESSAGE("Wobbuffet transformed into the Dark type!"); + MESSAGE("Wobbuffet transformed into the Normal type!"); + MESSAGE("Wobbuffet transformed into the Dark type!"); } } } @@ -84,6 +85,7 @@ SINGLE_BATTLE_TEST("Conversion 2's type change considers status moves (Gen 5+)") SINGLE_BATTLE_TEST("Conversion 2's type change considers the type of moves called by other moves") { GIVEN { + WITH_CONFIG(CONFIG_UPDATED_CONVERSION_2, GEN_5); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -94,8 +96,8 @@ SINGLE_BATTLE_TEST("Conversion 2's type change considers the type of moves calle MESSAGE("The opposing Wobbuffet used Mirror Move!"); // turn 2 ONE_OF { - MESSAGE("Wobbuffet transformed into the Normal type!"); - MESSAGE("Wobbuffet transformed into the Dark type!"); + MESSAGE("Wobbuffet transformed into the Normal type!"); + MESSAGE("Wobbuffet transformed into the Dark type!"); } } } @@ -103,6 +105,7 @@ SINGLE_BATTLE_TEST("Conversion 2's type change considers the type of moves calle SINGLE_BATTLE_TEST("Conversion 2's type change considers dynamic type moves") { GIVEN { + WITH_CONFIG(CONFIG_UPDATED_CONVERSION_2, GEN_5); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -113,10 +116,10 @@ SINGLE_BATTLE_TEST("Conversion 2's type change considers dynamic type moves") MESSAGE("The opposing Wobbuffet used Weather Ball!"); // turn 2 ONE_OF { - MESSAGE("Wobbuffet transformed into the Steel type!"); - MESSAGE("Wobbuffet transformed into the Fire type!"); - MESSAGE("Wobbuffet transformed into the Water type!"); - MESSAGE("Wobbuffet transformed into the Ice type!"); + MESSAGE("Wobbuffet transformed into the Steel type!"); + MESSAGE("Wobbuffet transformed into the Fire type!"); + MESSAGE("Wobbuffet transformed into the Water type!"); + MESSAGE("Wobbuffet transformed into the Ice type!"); } } } @@ -124,6 +127,7 @@ SINGLE_BATTLE_TEST("Conversion 2's type change considers dynamic type moves") SINGLE_BATTLE_TEST("Conversion 2's type change considers move types changed by Normalize and Electrify") { GIVEN { + WITH_CONFIG(CONFIG_UPDATED_CONVERSION_2, GEN_5); PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_NORMALIZE); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -136,17 +140,17 @@ SINGLE_BATTLE_TEST("Conversion 2's type change considers move types changed by N MESSAGE("The opposing Wobbuffet used Pound!"); // turn 2 ONE_OF { - MESSAGE("Wobbuffet transformed into the Ground type!"); - MESSAGE("Wobbuffet transformed into the Dragon type!"); - MESSAGE("Wobbuffet transformed into the Grass type!"); - MESSAGE("Wobbuffet transformed into the Electric type!"); + MESSAGE("Wobbuffet transformed into the Ground type!"); + MESSAGE("Wobbuffet transformed into the Dragon type!"); + MESSAGE("Wobbuffet transformed into the Grass type!"); + MESSAGE("Wobbuffet transformed into the Electric type!"); } // turn 3 MESSAGE("Wobbuffet used Water Gun!"); ONE_OF { - MESSAGE("The opposing Wobbuffet transformed into the Steel type!"); - MESSAGE("The opposing Wobbuffet transformed into the Rock type!"); - MESSAGE("The opposing Wobbuffet transformed into the Ghost type!"); + MESSAGE("The opposing Wobbuffet transformed into the Steel type!"); + MESSAGE("The opposing Wobbuffet transformed into the Rock type!"); + MESSAGE("The opposing Wobbuffet transformed into the Ghost type!"); } } } @@ -154,6 +158,7 @@ SINGLE_BATTLE_TEST("Conversion 2's type change considers move types changed by N SINGLE_BATTLE_TEST("Conversion 2's type change fails targeting Struggle (Gen 5+)") { GIVEN { + WITH_CONFIG(CONFIG_UPDATED_CONVERSION_2, GEN_5); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -171,6 +176,7 @@ SINGLE_BATTLE_TEST("Conversion 2's type change fails targeting Struggle (Gen 5+) SINGLE_BATTLE_TEST("Conversion 2 fails if the move used is of typeless damage (Gen 5+)") { GIVEN { + WITH_CONFIG(CONFIG_UPDATED_CONVERSION_2, GEN_5); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_ENTEI); } WHEN { @@ -187,7 +193,6 @@ SINGLE_BATTLE_TEST("Conversion 2 fails if the move used is of typeless damage (G MESSAGE("But it failed!"); } } -#endif SINGLE_BATTLE_TEST("Conversion 2 fails if the targeted move is Stellar Type") { @@ -205,3 +210,35 @@ SINGLE_BATTLE_TEST("Conversion 2 fails if the targeted move is Stellar Type") MESSAGE("But it failed!"); } } + +SINGLE_BATTLE_TEST("Conversion 2 fails if used by a Terastallized Pokemon") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + TURN { MOVE(player, MOVE_CONVERSION_2, gimmick: GIMMICK_TERA); } + } SCENE { + MESSAGE("Wobbuffet used Conversion 2!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Conversion 2 fails if last hit by a Stellar-type move (Gen 1-4)") +{ + GIVEN { + WITH_CONFIG(CONFIG_UPDATED_CONVERSION_2, GEN_4); + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_CONVERSION_2); } + } SCENE { + // turn 1 + MESSAGE("Wobbuffet used Tera Blast!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + // turn 2 + MESSAGE("The opposing Wobbuffet used Conversion 2!"); + MESSAGE("But it failed!"); + } +} From a901d227ccc90b73c8e000cc5ecf8926fa71d5c8 Mon Sep 17 00:00:00 2001 From: ghostyboyy97 <106448956+ghostyboyy97@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:40:46 -0500 Subject: [PATCH 12/13] fix (scoring): AI_IsMoveEffectInPlus - AI should not see secondary effect of Sheer Force boosted moves as beneficial (#8579) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- include/battle_util.h | 2 +- src/battle_ai_main.c | 9 +++------ src/battle_ai_util.c | 3 +++ src/battle_script_commands.c | 6 +++--- src/battle_util.c | 4 ++-- test/battle/ai/ai_calc_best_move_score.c | 13 +++++++++++++ 6 files changed, 25 insertions(+), 12 deletions(-) diff --git a/include/battle_util.h b/include/battle_util.h index 8ea72866e5..9ebc837952 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -343,7 +343,7 @@ bool32 IsPartnerMonFromSameTrainer(u32 battler); enum DamageCategory GetCategoryBasedOnStats(u32 battler); void SetShellSideArmCategory(void); bool32 MoveIsAffectedBySheerForce(u32 move); -bool32 TestIfSheerForceAffected(u32 battler, u16 move); +bool32 IsSheerForceAffected(u16 move, enum Ability ability); void TryRestoreHeldItems(void); bool32 CanStealItem(u32 battlerStealing, u32 battlerItem, u16 item); void TrySaveExchangedItem(u32 battler, u16 stolenItem); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 795efa9825..8dde9c4a97 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -5697,17 +5697,14 @@ static s32 AI_CalcAdditionalEffectScore(u32 battlerAtk, u32 battlerDef, u32 move u32 i; u32 additionalEffectCount = GetMoveAdditionalEffectCount(move); + if (IsSheerForceAffected(move, aiData->abilities[battlerAtk])) + return score; + // check move additional effects that are likely to happen for (i = 0; i < additionalEffectCount; i++) { const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(move, i); - if (aiData->abilities[battlerAtk] == ABILITY_SHEER_FORCE) - { - if ((additionalEffect->chance > 0) != additionalEffect->sheerForceOverride) - continue; - } - // Only consider effects with a guaranteed chance to happen if (!MoveEffectIsGuaranteed(battlerAtk, aiData->abilities[battlerAtk], additionalEffect)) continue; diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index a9c4b61284..20ebfdb27f 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -1030,6 +1030,9 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3 enum Ability abilityDef = gAiLogicData->abilities[battlerDef]; enum Ability abilityAtk = gAiLogicData->abilities[battlerAtk]; + if (IsSheerForceAffected(move, abilityAtk)) + return FALSE; + switch (GetMoveEffect(move)) { case EFFECT_HIT_ESCAPE: diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 516e817be5..5b7ddd9da9 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -3049,7 +3049,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c if (!primary && !affectsUser && IsMoveEffectBlockedByTarget(battlerAbility)) moveEffect = MOVE_EFFECT_NONE; else if (!primary - && TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove) + && IsSheerForceAffected(gCurrentMove, GetBattlerAbility(battler)) && !(GetMoveEffect(gCurrentMove) == EFFECT_ORDER_UP && gBattleStruct->battlerState[gBattlerAttacker].commanderSpecies != SPECIES_NONE)) moveEffect = MOVE_EFFECT_NONE; else if (!IsBattlerAlive(gEffectBattler) && !activateAfterFaint) @@ -6400,7 +6400,7 @@ static void Cmd_moveend(void) && gBattlerTarget != gBattlerAttacker && !IsBattlerAlly(gBattlerTarget, gBattlerAttacker) && gProtectStructs[gBattlerTarget].physicalBattlerId == gBattlerAttacker - && !TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)) + && !IsSheerForceAffected(gCurrentMove, GetBattlerAbility(gBattlerAttacker))) { gProtectStructs[gBattlerTarget].shellTrap = TRUE; // Change move order in double battles, so the hit mon with shell trap moves immediately after being hit. @@ -6660,7 +6660,7 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_SHEER_FORCE: - if (TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)) + if (IsSheerForceAffected(gCurrentMove, GetBattlerAbility(gBattlerAttacker))) gBattleScripting.moveendState = MOVEEND_EJECT_PACK; else gBattleScripting.moveendState++; diff --git a/src/battle_util.c b/src/battle_util.c index 3205529cb3..11a5ef583c 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -9892,9 +9892,9 @@ bool32 IsBattlerAffectedByHazards(u32 battler, bool32 toxicSpikes) return ret; } -bool32 TestIfSheerForceAffected(u32 battler, u16 move) +bool32 IsSheerForceAffected(u16 move, enum Ability ability) { - return GetBattlerAbility(battler) == ABILITY_SHEER_FORCE && MoveIsAffectedBySheerForce(move); + return ability == ABILITY_SHEER_FORCE && MoveIsAffectedBySheerForce(move); } // This function is the body of "jumpifstat", but can be used dynamically in a function diff --git a/test/battle/ai/ai_calc_best_move_score.c b/test/battle/ai/ai_calc_best_move_score.c index 88525249dc..7fcf0ced24 100644 --- a/test/battle/ai/ai_calc_best_move_score.c +++ b/test/battle/ai/ai_calc_best_move_score.c @@ -133,3 +133,16 @@ AI_SINGLE_BATTLE_TEST("HasMoveThatChangesKOThreshold - AI should not see self-ta TURN { MOVE(player, MOVE_HAMMER_ARM); EXPECT_MOVE(opponent, move == MOVE_EARTHQUAKE ? MOVE_NASTY_PLOT : MOVE_AURA_SPHERE); } } } + +AI_SINGLE_BATTLE_TEST("AI_IsMoveEffectInPlus - AI should not see secondary effect of Sheer Force boosted moves as beneficial") +{ + GIVEN { + ASSUME(GetMovePower(MOVE_PSYCHIC) == 90); + ASSUME(MoveHasAdditionalEffect(MOVE_PSYCHIC, MOVE_EFFECT_SP_DEF_MINUS_1) == TRUE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_STEELIX) { Level(100); Nature(NATURE_SASSY); Item(ITEM_STEELIXITE); Ability(ABILITY_STURDY); Speed(58); Moves(MOVE_GYRO_BALL); } + OPPONENT(SPECIES_BRAVIARY_HISUI) { Level(100); Nature(NATURE_TIMID); Ability(ABILITY_SHEER_FORCE); Speed(251); Moves(MOVE_PSYCHIC, MOVE_NIGHT_SHADE); } + } WHEN { + TURN { MOVE(player, MOVE_GYRO_BALL); SCORE_EQ_VAL(opponent, MOVE_PSYCHIC, 101); SCORE_EQ_VAL(opponent, MOVE_NIGHT_SHADE, 101); } + } +} From 44b658935f6d71b2a61c8cf543341b3173d3037c Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Fri, 19 Dec 2025 20:21:10 -0300 Subject: [PATCH 13/13] `B_TAILWIND_TURNS` config tests (#8592) --- include/constants/generational_changes.h | 2 +- src/battle_script_commands.c | 2 +- test/battle/move_effect/court_change.c | 3 ++- test/battle/move_effect/tailwind.c | 13 +++++++++---- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index ecbdd8a95e..755a4d1186 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -51,7 +51,7 @@ F(UPROAR_TURNS, uproarTurns, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(UPROAR_IGNORE_SOUNDPROOF, uproarIgnoreSoundproof, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(DISABLE_TURNS, disableTurns, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ - F(TAILWIND_TURNS, tailwindTurns, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ + F(TAILWIND_TURNS, tailwindTurns, (u32, GEN_COUNT - 1)) \ F(SLEEP_TURNS, sleepTurns, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(TAUNT_TURNS, tauntTurns, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(SPORT_TURNS, sportTurns, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 5b7ddd9da9..9dbbbe914d 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -11694,7 +11694,7 @@ static void Cmd_settailwind(void) if (!(gSideStatuses[side] & SIDE_STATUS_TAILWIND)) { gSideStatuses[side] |= SIDE_STATUS_TAILWIND; - gSideTimers[side].tailwindTimer = (B_TAILWIND_TURNS >= GEN_5 ? 4 : 3); + gSideTimers[side].tailwindTimer = (GetConfig(CONFIG_TAILWIND_TURNS) >= GEN_5 ? 4 : 3); gBattlescriptCurrInstr = cmd->nextInstr; } else diff --git a/test/battle/move_effect/court_change.c b/test/battle/move_effect/court_change.c index 40a1fe4e86..5ebe4c70f6 100644 --- a/test/battle/move_effect/court_change.c +++ b/test/battle/move_effect/court_change.c @@ -81,6 +81,7 @@ DOUBLE_BATTLE_TEST("Court Change swaps entry hazards used by the player") DOUBLE_BATTLE_TEST("Court Change used by the player swaps Mist, Safeguard, Aurora Veil, Reflect, Light Screen, Tailwind") { GIVEN { + WITH_CONFIG(CONFIG_TAILWIND_TURNS, GEN_5); PLAYER(SPECIES_WYNAUT); PLAYER(SPECIES_WYNAUT); PLAYER(SPECIES_WYNAUT); @@ -119,6 +120,7 @@ DOUBLE_BATTLE_TEST("Court Change used by the player swaps Mist, Safeguard, Auror DOUBLE_BATTLE_TEST("Court Change used by the opponent swaps Mist, Safeguard, Aurora Veil, Reflect, Light Screen, Tailwind") { GIVEN { + WITH_CONFIG(CONFIG_TAILWIND_TURNS, GEN_5); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); @@ -248,4 +250,3 @@ AI_SINGLE_BATTLE_TEST("AI uses Court Change") TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_COURT_CHANGE); } } } - diff --git a/test/battle/move_effect/tailwind.c b/test/battle/move_effect/tailwind.c index 9ed101301d..d22f36d285 100644 --- a/test/battle/move_effect/tailwind.c +++ b/test/battle/move_effect/tailwind.c @@ -6,10 +6,13 @@ ASSUMPTIONS ASSUME(GetMoveEffect(MOVE_TAILWIND) == EFFECT_TAILWIND); } -SINGLE_BATTLE_TEST("Tailwind applies for 4 turns") +SINGLE_BATTLE_TEST("Tailwind applies for 3 turns (Gen4) or 4 turns (Gen5+)") { + u32 config; + PARAMETRIZE { config = GEN_4; } + PARAMETRIZE { config = GEN_5; } GIVEN { - ASSUME(B_TAILWIND_TURNS >= GEN_5); + WITH_CONFIG(CONFIG_TAILWIND_TURNS, config); PLAYER(SPECIES_WOBBUFFET) { Speed(10); } OPPONENT(SPECIES_WOBBUFFET) { Speed(15); } } WHEN { @@ -28,8 +31,10 @@ SINGLE_BATTLE_TEST("Tailwind applies for 4 turns") MESSAGE("Wobbuffet used Celebrate!"); MESSAGE("The opposing Wobbuffet used Celebrate!"); - MESSAGE("Wobbuffet used Celebrate!"); - MESSAGE("The opposing Wobbuffet used Celebrate!"); + if (config >= GEN_5) { + MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } MESSAGE("The opposing Wobbuffet used Celebrate!"); MESSAGE("Wobbuffet used Celebrate!");