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] 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!"); + } +}