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] 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;