From 91325e83c185700d5cc65ef2f01dbba4b9c61bd4 Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Fri, 4 Oct 2024 10:00:12 +0100 Subject: [PATCH 1/2] Fixes to Protosynthesis, Quark Drive, Beast Boost, Orichalcum Pulse, Hadron Engine (#5447) * Protosynthesis & Quark Drive interactions; Fixes to Beast Boost/Protosynthesis/Quark Drive stat tie priority, Orichalcum Pulse and Hadron Engine stat boost * Protosynthesis + Quark Drive tests * Update src/battle_util.c * Update src/battle_util.c * Update src/battle_util.c * Update src/battle_util.c * Update src/battle_util.c * Update src/battle_util.c --------- Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- src/battle_main.c | 4 +- src/battle_script_commands.c | 5 +- src/battle_util.c | 40 ++++++--- test/battle/ability/protosynthesis.c | 125 ++++++++++++++++++++++++++- test/battle/ability/quark_drive.c | 106 +++++++++++++++++++++++ 5 files changed, 260 insertions(+), 20 deletions(-) diff --git a/src/battle_main.c b/src/battle_main.c index a9336c3f8b..3f4f140f65 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4743,9 +4743,9 @@ u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, u32 holdEffect) speed *= 2; else if (ability == ABILITY_SLOW_START && gDisableStructs[battler].slowStartTimer != 0) speed /= 2; - else if (ability == ABILITY_PROTOSYNTHESIS && (gBattleWeather & B_WEATHER_SUN || gBattleStruct->boosterEnergyActivates & gBitTable[battler])) + else if (ability == ABILITY_PROTOSYNTHESIS && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED) && ((gBattleWeather & B_WEATHER_SUN && WEATHER_HAS_EFFECT) || gBattleStruct->boosterEnergyActivates & (1u << battler))) speed = (GetHighestStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; - else if (ability == ABILITY_QUARK_DRIVE && (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gBattleStruct->boosterEnergyActivates & gBitTable[battler])) + else if (ability == ABILITY_QUARK_DRIVE && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED) && (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gBattleStruct->boosterEnergyActivates & (1u << battler))) speed = (GetHighestStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; // stat stages diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index aa92a7b884..f16ee0bf76 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -8777,12 +8777,15 @@ u32 GetHighestStatId(u32 battler) for (i = STAT_DEF; i < NUM_STATS; i++) { u16 *statVal = &gBattleMons[battler].attack + (i - 1); - if (*statVal > highestStat) + if (*statVal > highestStat && i != STAT_SPEED) { highestStat = *statVal; highestId = i; } } + if (gBattleMons[battler].speed > highestStat) + highestId = STAT_SPEED; + return highestId; } diff --git a/src/battle_util.c b/src/battle_util.c index 86e846d09c..2e463eaf58 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -6329,7 +6329,10 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_PROTOSYNTHESIS: - if (!gDisableStructs[battler].weatherAbilityDone && IsBattlerWeatherAffected(battler, B_WEATHER_SUN)) + if (!gDisableStructs[battler].weatherAbilityDone + && (gBattleWeather & B_WEATHER_SUN) && WEATHER_HAS_EFFECT + && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED) + && !(gBattleStruct->boosterEnergyActivates & (1u << battler))) { gDisableStructs[battler].weatherAbilityDone = TRUE; PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); @@ -6355,7 +6358,10 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_QUARK_DRIVE: - if (!gDisableStructs[battler].terrainAbilityDone && IsBattlerTerrainAffected(battler, STATUS_FIELD_ELECTRIC_TERRAIN)) + if (!gDisableStructs[battler].terrainAbilityDone + && gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN + && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED) + && !(gBattleStruct->boosterEnergyActivates & (1u << battler))) { gDisableStructs[battler].terrainAbilityDone = TRUE; PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); @@ -7594,7 +7600,8 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn) break; case HOLD_EFFECT_BOOSTER_ENERGY: if (!(gBattleStruct->boosterEnergyActivates & gBitTable[battler]) - && (((GetBattlerAbility(battler) == ABILITY_PROTOSYNTHESIS) && !(gBattleWeather & B_WEATHER_SUN)) + && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED) + && (((GetBattlerAbility(battler) == ABILITY_PROTOSYNTHESIS) && !((gBattleWeather & B_WEATHER_SUN) && WEATHER_HAS_EFFECT)) || ((GetBattlerAbility(battler) == ABILITY_QUARK_DRIVE) && !(gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN)))) { PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); @@ -7861,7 +7868,8 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn) break; case HOLD_EFFECT_BOOSTER_ENERGY: if (!(gBattleStruct->boosterEnergyActivates & gBitTable[battler]) - && (((GetBattlerAbility(battler) == ABILITY_PROTOSYNTHESIS) && !(gBattleWeather & B_WEATHER_SUN)) + && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED) + && (((GetBattlerAbility(battler) == ABILITY_PROTOSYNTHESIS) && !((gBattleWeather & B_WEATHER_SUN) && WEATHER_HAS_EFFECT)) || ((GetBattlerAbility(battler) == ABILITY_QUARK_DRIVE) && !(gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN)))) { PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); @@ -9285,25 +9293,27 @@ static inline u32 CalcMoveBasePowerAfterModifiers(u32 move, u32 battlerAtk, u32 case ABILITY_PROTOSYNTHESIS: { u8 atkHighestStat = GetHighestStatId(battlerAtk); - if ((weather & B_WEATHER_SUN || gBattleStruct->boosterEnergyActivates & gBitTable[battlerAtk]) - && ((IS_MOVE_PHYSICAL(move) && atkHighestStat == STAT_ATK) || (IS_MOVE_SPECIAL(move) && atkHighestStat == STAT_SPATK))) + if (((weather & B_WEATHER_SUN && WEATHER_HAS_EFFECT) || gBattleStruct->boosterEnergyActivates & (1u << battlerAtk)) + && ((IS_MOVE_PHYSICAL(move) && atkHighestStat == STAT_ATK) || (IS_MOVE_SPECIAL(move) && atkHighestStat == STAT_SPATK)) + && !(gBattleMons[battlerAtk].status2 & STATUS2_TRANSFORMED)) modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); } break; case ABILITY_QUARK_DRIVE: { u8 atkHighestStat = GetHighestStatId(battlerAtk); - if ((gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gBattleStruct->boosterEnergyActivates & gBitTable[battlerAtk]) - && ((IS_MOVE_PHYSICAL(move) && atkHighestStat == STAT_ATK) || (IS_MOVE_SPECIAL(move) && atkHighestStat == STAT_SPATK))) + if ((gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gBattleStruct->boosterEnergyActivates & (1u << battlerAtk)) + && ((IS_MOVE_PHYSICAL(move) && atkHighestStat == STAT_ATK) || (IS_MOVE_SPECIAL(move) && atkHighestStat == STAT_SPATK)) + && !(gBattleMons[battlerAtk].status2 & STATUS2_TRANSFORMED)) modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); } break; case ABILITY_ORICHALCUM_PULSE: - if (weather & B_WEATHER_SUN) + if (weather & B_WEATHER_SUN && WEATHER_HAS_EFFECT && IS_MOVE_PHYSICAL(move)) modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); break; case ABILITY_HADRON_ENGINE: - if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN) + if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN && IS_MOVE_SPECIAL(move)) modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); break; case ABILITY_SHARPNESS: @@ -9363,16 +9373,18 @@ static inline u32 CalcMoveBasePowerAfterModifiers(u32 move, u32 battlerAtk, u32 case ABILITY_PROTOSYNTHESIS: { u8 defHighestStat = GetHighestStatId(battlerDef); - if ((weather & B_WEATHER_SUN || gBattleStruct->boosterEnergyActivates & gBitTable[battlerDef]) - && ((IS_MOVE_PHYSICAL(move) && defHighestStat == STAT_DEF) || (IS_MOVE_SPECIAL(move) && defHighestStat == STAT_SPDEF))) + if (((weather & B_WEATHER_SUN && WEATHER_HAS_EFFECT) || gBattleStruct->boosterEnergyActivates & (1u << battlerDef)) + && ((IS_MOVE_PHYSICAL(move) && defHighestStat == STAT_DEF) || (IS_MOVE_SPECIAL(move) && defHighestStat == STAT_SPDEF)) + && !(gBattleMons[battlerDef].status2 & STATUS2_TRANSFORMED)) modifier = uq4_12_multiply(modifier, UQ_4_12(0.7)); } break; case ABILITY_QUARK_DRIVE: { u8 defHighestStat = GetHighestStatId(battlerDef); - if ((gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gBattleStruct->boosterEnergyActivates & gBitTable[battlerDef]) - && ((IS_MOVE_PHYSICAL(move) && defHighestStat == STAT_DEF) || (IS_MOVE_SPECIAL(move) && defHighestStat == STAT_SPDEF))) + if ((gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gBattleStruct->boosterEnergyActivates & (1u << battlerDef)) + && ((IS_MOVE_PHYSICAL(move) && defHighestStat == STAT_DEF) || (IS_MOVE_SPECIAL(move) && defHighestStat == STAT_SPDEF)) + && !(gBattleMons[battlerDef].status2 & STATUS2_TRANSFORMED)) modifier = uq4_12_multiply(modifier, UQ_4_12(0.7)); } break; diff --git a/test/battle/ability/protosynthesis.c b/test/battle/ability/protosynthesis.c index 58f10b366f..2be9f81d28 100644 --- a/test/battle/ability/protosynthesis.c +++ b/test/battle/ability/protosynthesis.c @@ -100,6 +100,125 @@ SINGLE_BATTLE_TEST("Protosynthesis activates on switch-in") } } -TO_DO_BATTLE_TEST("Protosynthesis activates in sun before Booster Energy"); -TO_DO_BATTLE_TEST("Protosynthesis activates even if the Pokémon is holding an Utility Umbrella"); -TO_DO_BATTLE_TEST("Protosynthesis doesn't activate if Cloud Nine/Air Lock is on the field"); +SINGLE_BATTLE_TEST("Protosynthesis boosts Attack 1st in case of a stat tie") +{ + GIVEN { + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_PROTOSYNTHESIS); Attack(5); Defense(5); SpAttack(5); SpDefense(5); Speed(5); } + OPPONENT(SPECIES_GROUDON) { Ability(ABILITY_DROUGHT); Speed(5); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("Great Tusk's Attack was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis boosts Defense 2nd in case of a stat tie") +{ + GIVEN { + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_PROTOSYNTHESIS); Attack(4); Defense(5); SpAttack(5); SpDefense(5); Speed(5); } + OPPONENT(SPECIES_GROUDON) { Ability(ABILITY_DROUGHT); Speed(5); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("Great Tusk's Defense was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis boosts Special Attack 3rd in case of a stat tie") +{ + GIVEN { + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_PROTOSYNTHESIS); Attack(4); Defense(4); SpAttack(5); SpDefense(5); Speed(5); } + OPPONENT(SPECIES_GROUDON) { Ability(ABILITY_DROUGHT); Speed(5); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("Great Tusk's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis boosts Special Defense 4th in case of a stat tie") +{ + GIVEN { + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_PROTOSYNTHESIS); Attack(4); Defense(4); SpAttack(4); SpDefense(5); Speed(5); } + OPPONENT(SPECIES_GROUDON) { Ability(ABILITY_DROUGHT); Speed(5); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("Great Tusk's Sp. Def was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis activates in Sun before Booster Energy") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_DROUGHT); } + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + } THEN { + EXPECT_EQ(player->item, ITEM_BOOSTER_ENERGY); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis doesn't activate for a transformed battler") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_DROUGHT); Item(ITEM_BOOSTER_ENERGY); } + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_TRANSFORM); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRANSFORM, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_PROTOSYNTHESIS); + } THEN { + EXPECT_EQ(player->item, ITEM_BOOSTER_ENERGY); + EXPECT_EQ(opponent->item, ITEM_BOOSTER_ENERGY); + EXPECT_EQ(opponent->ability, ABILITY_PROTOSYNTHESIS); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis activates even if the Pokémon is holding an Utility Umbrella") +{ + GIVEN { + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_PROTOSYNTHESIS); Item(ITEM_UTILITY_UMBRELLA); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_DROUGHT); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis doesn't activate if Cloud Nine/Air Lock is on the field") +{ + u32 species, ability; + PARAMETRIZE { species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + PARAMETRIZE { species = SPECIES_GOLDUCK; ability = ABILITY_CLOUD_NINE; } + + GIVEN { + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_PROTOSYNTHESIS); } + OPPONENT(species) { Ability(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ABILITY_POPUP(opponent, ability); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + } +} diff --git a/test/battle/ability/quark_drive.c b/test/battle/ability/quark_drive.c index 4e81b012ae..928ee45eb5 100644 --- a/test/battle/ability/quark_drive.c +++ b/test/battle/ability/quark_drive.c @@ -100,3 +100,109 @@ SINGLE_BATTLE_TEST("Quark Drive activates on switch-in") MESSAGE("Iron Moth's Sp. Atk was heightened!"); } } + +SINGLE_BATTLE_TEST("Quark Drive activates on Electric Terrain even if not grounded") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_IRON_JUGULIS].types[0] == TYPE_FLYING || gSpeciesInfo[SPECIES_IRON_JUGULIS].types[1] == TYPE_FLYING); + PLAYER(SPECIES_IRON_JUGULIS) { Ability(ABILITY_QUARK_DRIVE); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + } +} + +SINGLE_BATTLE_TEST("Quark Drive boosts Attack 1st in case of a stat tie") +{ + GIVEN { + PLAYER(SPECIES_IRON_TREADS) { Ability(ABILITY_QUARK_DRIVE); Attack(5); Defense(5); SpAttack(5); SpDefense(5); Speed(5); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); Speed(5); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("Iron Treads's Attack was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Quark Drive boosts Defense 2nd in case of a stat tie") +{ + GIVEN { + PLAYER(SPECIES_IRON_TREADS) { Ability(ABILITY_QUARK_DRIVE); Attack(4); Defense(5); SpAttack(5); SpDefense(5); Speed(5); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); Speed(5); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("Iron Treads's Defense was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Quark Drive boosts Special Attack 3rd in case of a stat tie") +{ + GIVEN { + PLAYER(SPECIES_IRON_TREADS) { Ability(ABILITY_QUARK_DRIVE); Attack(4); Defense(4); SpAttack(5); SpDefense(5); Speed(5); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); Speed(5); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("Iron Treads's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Quark Drive boosts Special Defense 4th in case of a stat tie") +{ + GIVEN { + PLAYER(SPECIES_IRON_TREADS) { Ability(ABILITY_QUARK_DRIVE); Attack(4); Defense(4); SpAttack(4); SpDefense(5); Speed(5); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); Speed(5); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("Iron Treads's Sp. Def was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Quark Drive activates in Electric Terrain before Booster Energy") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_IRON_TREADS) { Ability(ABILITY_QUARK_DRIVE); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); } + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + } THEN { + EXPECT_EQ(player->item, ITEM_BOOSTER_ENERGY); + } +} + +SINGLE_BATTLE_TEST("Quark Drive doesn't activate for a transformed battler") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_IRON_TREADS) { Ability(ABILITY_QUARK_DRIVE); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); Item(ITEM_BOOSTER_ENERGY); } + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_TRANSFORM); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRANSFORM, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_QUARK_DRIVE); + } THEN { + EXPECT_EQ(player->item, ITEM_BOOSTER_ENERGY); + EXPECT_EQ(opponent->item, ITEM_BOOSTER_ENERGY); + EXPECT_EQ(opponent->ability, ABILITY_QUARK_DRIVE); + } +} From 6f59d26753cc79a9526713d8935cc67b264704c8 Mon Sep 17 00:00:00 2001 From: wiz1989 <80073265+wiz1989@users.noreply.github.com> Date: Fri, 4 Oct 2024 18:42:15 +0200 Subject: [PATCH 2/2] updated Conversion 2 mechanics to 5+ (#5453) * updated Conversion 2 mechanics and added the toggle B_UPDATED_CONVERSION_2 * fixes and added new test cases * bugfixing and added EWRAM u16 gLastUsedMoveType * update after Pawkkie review --------- Co-authored-by: wiz1989 --- include/battle.h | 1 + include/config/battle.h | 1 + src/battle_main.c | 4 + src/battle_script_commands.c | 148 ++++++++++++----- src/data/moves_info.h | 2 +- test/battle/gimmick/terastal.c | 2 + test/battle/move_effect/conversion_2.c | 213 +++++++++++++++++++++++-- 7 files changed, 318 insertions(+), 53 deletions(-) diff --git a/include/battle.h b/include/battle.h index 582dcedfc2..636eb51cdb 100644 --- a/include/battle.h +++ b/include/battle.h @@ -1070,6 +1070,7 @@ extern u16 gLastPrintedMoves[MAX_BATTLERS_COUNT]; extern u16 gLastMoves[MAX_BATTLERS_COUNT]; extern u16 gLastLandedMoves[MAX_BATTLERS_COUNT]; extern u16 gLastHitByType[MAX_BATTLERS_COUNT]; +extern u16 gLastUsedMoveType[MAX_BATTLERS_COUNT]; extern u16 gLastResultingMoves[MAX_BATTLERS_COUNT]; extern u16 gLockedMoves[MAX_BATTLERS_COUNT]; extern u16 gLastUsedMove; diff --git a/include/config/battle.h b/include/config/battle.h index b00eb199a9..fbc41be6ba 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -70,6 +70,7 @@ #define B_RECOIL_IF_MISS_DMG GEN_LATEST // In Gen5+, Jump Kick and High Jump Kick will always do half of the user's max HP when missing. #define B_KLUTZ_FLING_INTERACTION GEN_LATEST // In Gen5+, Pokémon with the Klutz ability can't use Fling. #define B_UPDATED_CONVERSION GEN_LATEST // In Gen6+, Conversion changes the user's type to match their first move's. Before, it would choose a move at random. +#define B_UPDATED_CONVERSION_2 GEN_LATEST // In Gen5+, Conversion 2 changes the user's type to a type that resists the last move used by the selected target. Before, it would consider the last move being successfully hit by. Additionally, Struggle is considered Normal type before Gen 5. #define B_PP_REDUCED_BY_SPITE GEN_LATEST // In Gen4+, Spite reduces the foe's last move's PP by 4, instead of 2 to 5. #define B_EXTRAPOLATED_MOVE_FLAGS TRUE // Adds move flags to moves that they don't officially have but would likely have if they were in the latest core series game. diff --git a/src/battle_main.c b/src/battle_main.c index 3f4f140f65..e0ec3023ec 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -181,6 +181,7 @@ EWRAM_DATA u16 gLastPrintedMoves[MAX_BATTLERS_COUNT] = {0}; EWRAM_DATA u16 gLastMoves[MAX_BATTLERS_COUNT] = {0}; EWRAM_DATA u16 gLastLandedMoves[MAX_BATTLERS_COUNT] = {0}; EWRAM_DATA u16 gLastHitByType[MAX_BATTLERS_COUNT] = {0}; +EWRAM_DATA u16 gLastUsedMoveType[MAX_BATTLERS_COUNT] = {0}; EWRAM_DATA u16 gLastResultingMoves[MAX_BATTLERS_COUNT] = {0}; EWRAM_DATA u16 gLockedMoves[MAX_BATTLERS_COUNT] = {0}; EWRAM_DATA u16 gLastUsedMove = 0; @@ -3029,6 +3030,7 @@ static void BattleStartClearSetData(void) gLastMoves[i] = MOVE_NONE; gLastLandedMoves[i] = MOVE_NONE; gLastHitByType[i] = 0; + gLastUsedMoveType[i] = 0; gLastResultingMoves[i] = MOVE_NONE; gLastHitBy[i] = 0xFF; gLockedMoves[i] = MOVE_NONE; @@ -3207,6 +3209,7 @@ void SwitchInClearSetData(u32 battler) gLastMoves[battler] = MOVE_NONE; gLastLandedMoves[battler] = MOVE_NONE; gLastHitByType[battler] = 0; + gLastUsedMoveType[battler] = 0; gLastResultingMoves[battler] = MOVE_NONE; gLastPrintedMoves[battler] = MOVE_NONE; gLastHitBy[battler] = 0xFF; @@ -3336,6 +3339,7 @@ const u8* FaintClearSetData(u32 battler) gLastMoves[battler] = MOVE_NONE; gLastLandedMoves[battler] = MOVE_NONE; gLastHitByType[battler] = 0; + gLastUsedMoveType[battler] = 0; gLastResultingMoves[battler] = MOVE_NONE; gLastPrintedMoves[battler] = MOVE_NONE; gLastHitBy[battler] = 0xFF; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index f16ee0bf76..ba372d1c54 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -5916,12 +5916,14 @@ static void Cmd_moveend(void) gLastMoves[gBattlerAttacker] = gChosenMove; RecordKnownMove(gBattlerAttacker, gChosenMove); gLastResultingMoves[gBattlerAttacker] = gCurrentMove; + GET_MOVE_TYPE(gCurrentMove, gLastUsedMoveType[gBattlerAttacker]); } } else { gLastMoves[gBattlerAttacker] = MOVE_UNAVAILABLE; gLastResultingMoves[gBattlerAttacker] = MOVE_UNAVAILABLE; + gLastUsedMoveType[gBattlerAttacker] = 0; } if (!(gHitMarker & HITMARKER_FAINTED(gBattlerTarget))) @@ -12953,60 +12955,122 @@ static void Cmd_settypetorandomresistance(void) { CMD_ARGS(const u8 *failInstr); - if (gLastLandedMoves[gBattlerAttacker] == MOVE_NONE - || gLastLandedMoves[gBattlerAttacker] == MOVE_UNAVAILABLE) + // 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) { - gBattlescriptCurrInstr = cmd->failInstr; - } - else if (gBattleMoveEffects[gMovesInfo[gLastLandedMoves[gBattlerAttacker]].effect].twoTurnEffect - && gBattleMons[gLastHitBy[gBattlerAttacker]].status2 & STATUS2_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; + if (gLastLandedMoves[gBattlerAttacker] == MOVE_NONE + || gLastLandedMoves[gBattlerAttacker] == MOVE_UNAVAILABLE) + { + gBattlescriptCurrInstr = cmd->failInstr; + } + else if (gBattleMoveEffects[gMovesInfo[gLastLandedMoves[gBattlerAttacker]].effect].twoTurnEffect + && gBattleMons[gLastHitBy[gBattlerAttacker]].status2 & STATUS2_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; + } + 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 |= gBitTable[i]; + break; + } + } + + while (resistTypes != 0) + { + i = Random() % NUMBER_OF_MON_TYPES; + if (resistTypes & gBitTable[i]) + { + if (IS_BATTLER_OF_TYPE(gBattlerAttacker, i)) + { + resistTypes &= ~(gBitTable[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; + } } else { - u32 i, resistTypes = 0; - u32 hitByType = gLastHitByType[gBattlerAttacker]; - - for (i = 0; i < NUMBER_OF_MON_TYPES; i++) // Find all types that resist. + if (gLastResultingMoves[gBattlerTarget] == MOVE_NONE + || gLastResultingMoves[gBattlerTarget] == MOVE_UNAVAILABLE + || gLastResultingMoves[gBattlerTarget] == MOVE_STRUGGLE) { - switch (GetTypeModifier(hitByType, i)) - { - case UQ_4_12(0): - case UQ_4_12(0.5): - resistTypes |= gBitTable[i]; - break; - } + gBattlescriptCurrInstr = cmd->failInstr; } - - while (resistTypes != 0) + else if (IsSemiInvulnerable(gBattlerTarget, gCurrentMove)) { - i = Random() % NUMBER_OF_MON_TYPES; - if (resistTypes & gBitTable[i]) + 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; + + for (i = 0; i < NUMBER_OF_MON_TYPES; i++) // Find all types that resist. { - if (IS_BATTLER_OF_TYPE(gBattlerAttacker, i)) + switch (GetTypeModifier(gLastUsedMoveType[gBattlerTarget], i)) { - resistTypes &= ~(gBitTable[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; + case UQ_4_12(0): + case UQ_4_12(0.5): + resistTypes |= gBitTable[i]; + break; } } - } - gBattlescriptCurrInstr = cmd->failInstr; + while (resistTypes != 0) + { + i = Random() % NUMBER_OF_MON_TYPES; + if (resistTypes & gBitTable[i]) + { + if (IS_BATTLER_OF_TYPE(gBattlerAttacker, i)) + { + resistTypes &= ~(gBitTable[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; + } } } diff --git a/src/data/moves_info.h b/src/data/moves_info.h index b6d0aaf3ca..cb23dcf5af 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -4511,7 +4511,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .type = TYPE_NORMAL, .accuracy = 0, .pp = 30, - .target = MOVE_TARGET_USER, + .target = B_UPDATED_MOVE_DATA >= GEN_5 ? MOVE_TARGET_SELECTED : MOVE_TARGET_USER, .priority = 0, .category = DAMAGE_CATEGORY_STATUS, .zMove = { .effect = Z_EFFECT_RECOVER_HP }, diff --git a/test/battle/gimmick/terastal.c b/test/battle/gimmick/terastal.c index 1b50bc4bcc..0484fcd497 100644 --- a/test/battle/gimmick/terastal.c +++ b/test/battle/gimmick/terastal.c @@ -509,6 +509,7 @@ 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 { @@ -526,6 +527,7 @@ SINGLE_BATTLE_TEST("(TERA) Conversion2 fails if last hit by a Stellar-type move" MESSAGE("But it failed!"); } } +#endif SINGLE_BATTLE_TEST("(TERA) Roost does not remove Flying-type ground immunity when Terastallized into the Stellar type") { diff --git a/test/battle/move_effect/conversion_2.c b/test/battle/move_effect/conversion_2.c index 2e81212ef1..6c2d4d7f54 100644 --- a/test/battle/move_effect/conversion_2.c +++ b/test/battle/move_effect/conversion_2.c @@ -1,14 +1,207 @@ #include "global.h" #include "test/battle.h" -TO_DO_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)"); -TO_DO_BATTLE_TEST("Conversion 2 randomly changes the type of the user to a type that resists the last move used by the target (Gen 5+)"); -TO_DO_BATTLE_TEST("Conversion 2's type change considers the type of moves called by other moves"); -TO_DO_BATTLE_TEST("Conversion 2's type change considers dynamic type moves"); // Eg. Weather Ball -TO_DO_BATTLE_TEST("Conversion 2's type change considers move types changed by Normalize and Electrify"); -TO_DO_BATTLE_TEST("Conversion 2's type change considers move types changed by Normalize"); -TO_DO_BATTLE_TEST("Conversion 2's type change considers Struggle to be Normal type (Gen 3-4)"); -TO_DO_BATTLE_TEST("Conversion 2 fails if the move used is of typeless damage (Gen 5+)"); -TO_DO_BATTLE_TEST("Conversion 2's type change considers status moves (Gen 5+)"); TO_DO_BATTLE_TEST("Conversion 2's type change considers Inverse Battles"); -TO_DO_BATTLE_TEST("Conversion 2 fails if the move used is Stellar Type"); + +#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)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_OMINOUS_WIND); MOVE(opponent, MOVE_CONVERSION_2); } + } SCENE { + // turn 1 + MESSAGE("Wobbuffet used Ominous Wind!"); + // turn 1 + ONE_OF { + MESSAGE("Foe Wobbuffet transformed into the Normal type!"); + MESSAGE("Foe Wobbuffet transformed into the Dark type!"); + } + } +} + +SINGLE_BATTLE_TEST("Conversion 2's type change considers Struggle to be Normal type (Gen 3-4)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_STRUGGLE); } + TURN { MOVE(player, MOVE_CONVERSION_2); } + } SCENE { + // turn 1 + MESSAGE("Foe 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!"); + } + } +} +#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 { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_OMINOUS_WIND); MOVE(opponent, MOVE_CONVERSION_2); } + } SCENE { + // turn 1 + MESSAGE("Wobbuffet used Ominous Wind!"); + // turn 1 + ONE_OF { + MESSAGE("Foe Wobbuffet transformed into the Normal type!"); + MESSAGE("Foe Wobbuffet transformed into the Dark type!"); + } + } +} + +SINGLE_BATTLE_TEST("Conversion 2's type change considers status moves (Gen 5+)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_CURSE); } + TURN { MOVE(player, MOVE_CONVERSION_2); } + } SCENE { + // turn 1 + MESSAGE("Foe Wobbuffet used Curse!"); + // turn 2 + ONE_OF { + MESSAGE("Wobbuffet transformed into the Normal type!"); + MESSAGE("Wobbuffet transformed into the Dark type!"); + } + } +} + +SINGLE_BATTLE_TEST("Conversion 2's type change considers the type of moves called by other moves") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_OMINOUS_WIND); MOVE(opponent, MOVE_MIRROR_MOVE); } + TURN { MOVE(player, MOVE_CONVERSION_2); } + } SCENE { + // turn 1 + MESSAGE("Foe Wobbuffet used Mirror Move!"); + // turn 2 + ONE_OF { + MESSAGE("Wobbuffet transformed into the Normal type!"); + MESSAGE("Wobbuffet transformed into the Dark type!"); + } + } +} + +SINGLE_BATTLE_TEST("Conversion 2's type change considers dynamic type moves") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HAIL); MOVE(opponent, MOVE_WEATHER_BALL); } + TURN { MOVE(player, MOVE_CONVERSION_2); } + } SCENE { + // turn 1 + MESSAGE("Foe 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!"); + } + } +} + +SINGLE_BATTLE_TEST("Conversion 2's type change considers move types changed by Normalize and Electrify") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_NORMALIZE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIFY); MOVE(opponent, MOVE_POUND); } + TURN { MOVE(player, MOVE_CONVERSION_2); } + TURN { MOVE(player, MOVE_WATER_GUN); MOVE(opponent, MOVE_CONVERSION_2); } + } SCENE { + // turn 1 + MESSAGE("Wobbuffet used Electrify!"); + MESSAGE("Foe 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!"); + } + // turn 3 + MESSAGE("Wobbuffet used Water Gun!"); + ONE_OF { + MESSAGE("Foe Wobbuffet transformed into the Steel type!"); + MESSAGE("Foe Wobbuffet transformed into the Rock type!"); + MESSAGE("Foe Wobbuffet transformed into the Ghost type!"); + } + } +} + +SINGLE_BATTLE_TEST("Conversion 2's type change fails targeting Struggle (Gen 5+)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_STRUGGLE); } + TURN { MOVE(player, MOVE_CONVERSION_2); } + } SCENE { + // turn 1 + MESSAGE("Foe Wobbuffet used Struggle!"); + // turn 2 + MESSAGE("Wobbuffet used Conversion 2!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Conversion 2 fails if the move used is of typeless damage (Gen 5+)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ENTEI); + } WHEN { + TURN { MOVE(opponent, MOVE_BURN_UP); } + TURN { MOVE(opponent, MOVE_REVELATION_DANCE); } + TURN { MOVE(player, MOVE_CONVERSION_2); } + } SCENE { + // turn 1 + MESSAGE("Foe Entei used Burn Up!"); + // turn 2 + MESSAGE("Foe Entei used Revelation Dance!"); + // turn 3 + MESSAGE("Wobbuffet used Conversion 2!"); + MESSAGE("But it failed!"); + } +} +#endif + +SINGLE_BATTLE_TEST("Conversion 2 fails if the targeted move is Stellar Type") +{ + GIVEN { + 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 1 + MESSAGE("Foe Wobbuffet used Conversion 2!"); + MESSAGE("But it failed!"); + } +}