Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes Hit Escape moves interaction with hold effects and switch in ab… #4091

Merged
merged 4 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ Based off RHH's pokeemerald-expansion v1.7.2 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**:
Expand Down
2 changes: 2 additions & 0 deletions data/battle_scripts_1.s
Original file line number Diff line number Diff line change
Expand Up @@ -8688,6 +8688,7 @@ BattleScript_TryAdrenalineOrbRet:

BattleScript_IntimidateActivates::
showabilitypopup BS_ATTACKER
copybyte sSAVED_BATTLER, gBattlerTarget
pause B_WAIT_TIME_LONG
destroyabilitypopup
setbyte gBattlerTarget, 0
Expand Down Expand Up @@ -8715,6 +8716,7 @@ BattleScript_IntimidateLoopIncrement:
BattleScript_IntimidateEnd:
copybyte sBATTLER, gBattlerAttacker
destroyabilitypopup
copybyte gBattlerTarget, sSAVED_BATTLER
pause B_WAIT_TIME_MED
end3

Expand Down
1 change: 0 additions & 1 deletion include/battle.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion include/battle_script_commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
43 changes: 13 additions & 30 deletions src/battle_script_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand All @@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to make sure that when this PR makes it to upcoming, we apply the Intimidate changes to Supersweet Syrup too

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already tried to include it until I realized this goes to master. Will make sure to take care of it.

&& !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;
Expand Down
18 changes: 3 additions & 15 deletions src/battle_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -3290,7 +3290,7 @@ bool32 HandleWishPerishSongOnTurnEnd(void)
return FALSE;
}

#define FAINTED_ACTIONS_MAX_CASE 8
#define FAINTED_ACTIONS_MAX_CASE 7

bool32 HandleFaintedMonActions(void)
{
Expand Down Expand Up @@ -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++;
Expand Down Expand Up @@ -4673,8 +4661,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++;
Expand Down
10 changes: 5 additions & 5 deletions test/battle/ability/download.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
45 changes: 35 additions & 10 deletions test/battle/ability/intimidate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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!");
}
}
}

Expand Down Expand Up @@ -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);
}
}
20 changes: 20 additions & 0 deletions test/battle/form_change/primal_reversion.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
66 changes: 66 additions & 0 deletions test/battle/move_effect/hit_escape.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Loading