diff --git a/README.md b/README.md index 9f2e022dd6..d4fe25ea99 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ Based off RHH's pokeemerald-expansion v1.7.3 https://github.com/rh-hideout/pokee - Accesible by pressing `R + Start` in the overworld by default. - **Additional features**: - *Clear Boxes*: cleans every Pokémon from the Boxes. - - *Hatch an Egg*: lets you choose an Egg in your party and immediatly hatch it. + - *Hatch an Egg*: lets you choose an Egg in your party and immediately hatch it. - [HGSS Pokédex](https://github.com/TheXaman/pokeemerald/tree/tx_pokedexPlus_hgss) by @TheXaman - May be disabled. - **Additional features**: diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index c817ffc4bb..b721d72202 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -8669,6 +8669,7 @@ BattleScript_TryAdrenalineOrbRet: BattleScript_IntimidateActivates:: showabilitypopup BS_ATTACKER + copybyte sSAVED_BATTLER, gBattlerTarget pause B_WAIT_TIME_LONG destroyabilitypopup setbyte gBattlerTarget, 0 @@ -8696,6 +8697,7 @@ BattleScript_IntimidateLoopIncrement: BattleScript_IntimidateEnd: copybyte sBATTLER, gBattlerAttacker destroyabilitypopup + copybyte gBattlerTarget, sSAVED_BATTLER pause B_WAIT_TIME_MED end3 diff --git a/include/battle.h b/include/battle.h index 0eba532cd9..3a7a9071b4 100644 --- a/include/battle.h +++ b/include/battle.h @@ -703,7 +703,6 @@ struct BattleStruct u8 quickClawBattlerId; struct LostItem itemLost[PARTY_SIZE]; // Player's team that had items consumed or stolen (two bytes per party member) u8 forcedSwitch:4; // For each battler - u8 switchInAbilityPostponed:4; // To not activate against an empty field, each bit for battler u8 blunderPolicy:1; // should blunder policy activate u8 swapDamageCategory:1; // Photon Geyser, Shell Side Arm, Light That Burns the Sky u8 ballSpriteIds[2]; // item gfx, window gfx diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index a4e8a166ed..87a2a33280 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -54,7 +54,7 @@ u8 GetCatchingBattler(void); u32 GetHighestStatId(u32 battlerId); bool32 ProteanTryChangeType(u32 battler, u32 ability, u32 move, u32 moveType); bool32 IsMoveNotAllowedInSkyBattles(u32 move); -bool32 DoSwitchInAbilitiesItems(u32 battlerId); +bool32 DoSwitchInAbilities(u32 battlerId); u8 GetFirstFaintedPartyIndex(u8 battlerId); bool32 IsMoveAffectedByParentalBond(u32 move, u32 battler); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 606e5f0c93..cb33eaa1e8 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6777,34 +6777,13 @@ static void SetDmgHazardsBattlescript(u8 battler, u8 multistringId) gBattlescriptCurrInstr = BattleScript_DmgHazardsOnFaintedBattler; } -bool32 DoSwitchInAbilitiesItems(u32 battler) +bool32 DoSwitchInAbilities(u32 battler) { return (TryPrimalReversion(battler) - || AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0, 0) - || (gBattleWeather & B_WEATHER_ANY && WEATHER_HAS_EFFECT && AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, 0, 0, 0)) - || (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, 0, 0, 0)) - || ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, battler, FALSE) - || AbilityBattleEffects(ABILITYEFFECT_TRACE2, 0, 0, 0, 0)); -} - -bool32 ShouldPostponeSwitchInAbilities(u32 battler) -{ - bool32 aliveOpposing1 = IsBattlerAlive(BATTLE_OPPOSITE(battler)); - bool32 aliveOpposing2 = IsBattlerAlive(BATTLE_PARTNER(BATTLE_OPPOSITE(battler))); - // No pokemon on opposing side - postpone. - if (!aliveOpposing1 && !aliveOpposing2) - return TRUE; - - // Checks for double battle, so abilities like Intimidate wait until all battlers are switched-in before activating. - if (IsDoubleBattle()) - { - if (aliveOpposing1 && !aliveOpposing2 && !HasNoMonsToSwitch(BATTLE_PARTNER(BATTLE_OPPOSITE(battler)), PARTY_SIZE, PARTY_SIZE)) - return TRUE; - if (!aliveOpposing1 && aliveOpposing2 && !HasNoMonsToSwitch(BATTLE_OPPOSITE(battler), PARTY_SIZE, PARTY_SIZE)) - return TRUE; - } - - return FALSE; + || AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0, 0) + || (gBattleWeather & B_WEATHER_ANY && WEATHER_HAS_EFFECT && AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, 0, 0, 0)) + || (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, 0, 0, 0)) + || AbilityBattleEffects(ABILITYEFFECT_TRACE2, 0, 0, 0, 0)); } static void Cmd_switchineffects(void) @@ -6943,9 +6922,10 @@ static void Cmd_switchineffects(void) } else { + u32 battlerAbility = GetBattlerAbility(battler); // There is a hack here to ensure the truant counter will be 0 when the battler's next turn starts. // The truant counter is not updated in the case where a mon switches in after a lost judgment in the battle arena. - if (GetBattlerAbility(battler) == ABILITY_TRUANT + if (battlerAbility == ABILITY_TRUANT && gCurrentActionFuncId != B_ACTION_USE_MOVE && !gDisableStructs[battler].truantSwitchInHack) gDisableStructs[battler].truantCounter = 1; @@ -6954,13 +6934,16 @@ static void Cmd_switchineffects(void) // Don't activate switch-in abilities if the opposing field is empty. // This could happen when a mon uses explosion and causes everyone to faint. - if (ShouldPostponeSwitchInAbilities(battler) || gBattleStruct->switchInAbilityPostponed) + if ((battlerAbility == ABILITY_INTIMIDATE || battlerAbility == ABILITY_DOWNLOAD) + && !IsBattlerAlive(BATTLE_OPPOSITE(battler)) + && !IsBattlerAlive(BATTLE_PARTNER(BATTLE_OPPOSITE(battler)))) { - gBattleStruct->switchInAbilityPostponed |= gBitTable[battler]; + if (ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, battler, FALSE)) + return; } else { - if (DoSwitchInAbilitiesItems(battler)) + if (DoSwitchInAbilities(battler) || ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, battler, FALSE)) return; else if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0)) return; diff --git a/src/battle_util.c b/src/battle_util.c index fe4863c0f0..0674329c3a 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3290,7 +3290,7 @@ bool32 HandleWishPerishSongOnTurnEnd(void) return FALSE; } -#define FAINTED_ACTIONS_MAX_CASE 8 +#define FAINTED_ACTIONS_MAX_CASE 7 bool32 HandleFaintedMonActions(void) { @@ -3374,19 +3374,7 @@ bool32 HandleFaintedMonActions(void) else gBattleStruct->faintedActionsState = 4; break; - case 6: // All battlers switch-in abilities happen here to prevent them happening against an empty field. - for (i = 0; i < gBattlersCount; i++) - { - if (gBattleStruct->switchInAbilityPostponed & gBitTable[i]) - { - if (DoSwitchInAbilitiesItems(i)) - return TRUE; - gBattleStruct->switchInAbilityPostponed &= ~(gBitTable[i]); - } - } - gBattleStruct->faintedActionsState++; - break; - case 7: + case 6: if (ItemBattleEffects(ITEMEFFECT_NORMAL, 0, TRUE)) return TRUE; gBattleStruct->faintedActionsState++; @@ -4677,8 +4665,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_INTIMIDATE: if (!gSpecialStatuses[battler].switchInAbilityDone) { - gBattlerAttacker = battler; gSpecialStatuses[battler].switchInAbilityDone = TRUE; + gBattlerAttacker = battler; SET_STATCHANGER(STAT_ATK, 1, TRUE); BattleScriptPushCursorAndCallback(BattleScript_IntimidateActivates); effect++; diff --git a/test/battle/ability/download.c b/test/battle/ability/download.c index 5b59e37f00..9af3f4ba9e 100644 --- a/test/battle/ability/download.c +++ b/test/battle/ability/download.c @@ -75,21 +75,21 @@ SINGLE_BATTLE_TEST("Download doesn't activate if target hasn't been sent out yet MESSAGE("Go! Porygon!"); MESSAGE("2 sent out Porygon2!"); - if (ability == ABILITY_DOWNLOAD) - { + NONE_OF { ABILITY_POPUP(player, ABILITY_DOWNLOAD); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Porygon's Download raised its Attack!"); + } + if (ability == ABILITY_DOWNLOAD) + { ABILITY_POPUP(opponent, ABILITY_DOWNLOAD); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); MESSAGE("Foe Porygon2's Download raised its Sp. Atk!"); } - ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); - HP_BAR(opponent, captureDamage: &results[i].damagePhysical); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRI_ATTACK, opponent); HP_BAR(player, captureDamage: &results[i].damageSpecial); } FINALLY { - EXPECT_MUL_EQ(results[0].damagePhysical, Q_4_12(1.5), results[1].damagePhysical); EXPECT_MUL_EQ(results[0].damageSpecial, Q_4_12(1.5), results[1].damageSpecial); } } diff --git a/test/battle/ability/intimidate.c b/test/battle/ability/intimidate.c index a0a23cc132..8c87aec6db 100644 --- a/test/battle/ability/intimidate.c +++ b/test/battle/ability/intimidate.c @@ -82,17 +82,19 @@ DOUBLE_BATTLE_TEST("Intimidate doesn't activate on an empty field in a double ba MESSAGE("Go! Abra!"); MESSAGE("2 sent out Wynaut!"); - ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); - MESSAGE("Ekans's Intimidate cuts Foe Arbok's attack!"); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); - MESSAGE("Ekans's Intimidate cuts Foe Wynaut's attack!"); + NONE_OF { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Ekans's Intimidate cuts Foe Arbok's attack!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Ekans's Intimidate cuts Foe Wynaut's attack!"); - ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); - MESSAGE("Foe Arbok's Intimidate cuts Ekans's attack!"); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); - MESSAGE("Foe Arbok's Intimidate cuts Abra's attack!"); + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Foe Arbok's Intimidate cuts Ekans's attack!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Foe Arbok's Intimidate cuts Abra's attack!"); + } } } @@ -159,3 +161,26 @@ DOUBLE_BATTLE_TEST("Intimidate activates on an empty slot") MESSAGE("Hitmontop's Intimidate cuts Foe Azurill's attack!"); } } + +DOUBLE_BATTLE_TEST("Intimidate activates immediately after the mon was switched in as long as one opposing mon is alive") +{ + GIVEN { + PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); Item(ITEM_ELECTRIC_SEED); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_U_TURN, target: opponentLeft); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 2); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_ELECTRIC_SURGE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, playerLeft); + HP_BAR(opponentLeft); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} diff --git a/test/battle/form_change/primal_reversion.c b/test/battle/form_change/primal_reversion.c index 3e271f67c2..8419d78dca 100644 --- a/test/battle/form_change/primal_reversion.c +++ b/test/battle/form_change/primal_reversion.c @@ -214,3 +214,23 @@ SINGLE_BATTLE_TEST("Primal reversion happens after the entry hazards damage") EXPECT_EQ(player->species, SPECIES_GROUDON_PRIMAL); } } + +SINGLE_BATTLE_TEST("Primal reversion happens immediately if it was brought in by U-turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_GROUDON) { Item(ITEM_RED_ORB); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, player); + MESSAGE("Groudon's Primal Reversion! It reverted to its primal form!"); + MESSAGE("2 sent out Wynaut!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_GROUDON_PRIMAL); + } +} diff --git a/test/battle/move_effect/hit_escape.c b/test/battle/move_effect/hit_escape.c index b9d1bc99e7..68f0cb7dde 100644 --- a/test/battle/move_effect/hit_escape.c +++ b/test/battle/move_effect/hit_escape.c @@ -112,3 +112,69 @@ SINGLE_BATTLE_TEST("U-turn switches the user out after Ice Face activates") MESSAGE("Go! Wynaut!"); } } + +SINGLE_BATTLE_TEST("Held items are consumed immediately after a mon switched in by U-turn and Intimidate activates after it: player side") +{ + GIVEN { + PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); Item(ITEM_ELECTRIC_SEED); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("2 sent out Wynaut!"); + NOT ABILITY_POPUP(player, ABILITY_INTIMIDATE); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Held items are consumed immediately after a mon switched in by U-turn and Intimidate activates after it: opposing side") +{ + GIVEN { + PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WYNAUT) { Item(ITEM_ELECTRIC_SEED); } + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + NOT ABILITY_POPUP(player, ABILITY_INTIMIDATE); + MESSAGE("2 sent out Wynaut!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + NOT ABILITY_POPUP(player, ABILITY_INTIMIDATE); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Electric Seed boost is received by the right pokemon after U-turn and Intimidate") +{ + GIVEN { + PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); Item(ITEM_ELECTRIC_SEED); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +}