From 31ac151e29faf60ab322f0e2453826a016f84b47 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Fri, 9 Feb 2024 13:58:16 +0100 Subject: [PATCH 1/3] Fix Full Restore / Antidote not reseting Toxic Counter (#4135) * Fix Full Restore / Antidote not reseting Toxic Counter * Update battle_scripts_2.s --------- Co-authored-by: Bassoonian --- asm/macros/battle_script.inc | 6 +- data/battle_scripts_2.s | 34 +++-- src/battle_script_commands.c | 135 ++++++++++-------- src/item.c | 4 +- test/battle/item_effect/cure_status.c | 18 +++ .../battle/item_effect/heal_and_cure_status.c | 51 ++++++- 6 files changed, 168 insertions(+), 80 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index dd5d10296d..99ee9a4e5e 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1332,12 +1332,14 @@ .4byte \jumpInstr .endm - .macro itemrestorehp + .macro itemrestorehp jumpInstr:req callnative BS_ItemRestoreHP + .4byte \jumpInstr .endm - .macro itemcurestatus + .macro itemcurestatus jumpInstr:req callnative BS_ItemCureStatus + .4byte \jumpInstr .endm .macro itemincreasestat diff --git a/data/battle_scripts_2.s b/data/battle_scripts_2.s index 78e077fbd5..6895be7966 100644 --- a/data/battle_scripts_2.s +++ b/data/battle_scripts_2.s @@ -45,16 +45,21 @@ BattleScript_UseItemMessage: printfromtable gTrainerUsedItemStringIds waitmessage B_WAIT_TIME_LONG return - -BattleScript_ItemRestoreHP:: - call BattleScript_UseItemMessage - itemrestorehp + +BattleScript_ItemRestoreHPRet: bichalfword gMoveResultFlags, MOVE_RESULT_NO_EFFECT orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE healthbarupdate BS_SCRIPTING datahpupdate BS_SCRIPTING printstring STRINGID_ITEMRESTOREDSPECIESHEALTH waitmessage B_WAIT_TIME_LONG + return + +BattleScript_ItemRestoreHP:: + call BattleScript_UseItemMessage + itemrestorehp BattleScript_ItemRestoreHPEnd + call BattleScript_ItemRestoreHPRet +BattleScript_ItemRestoreHPEnd: end BattleScript_ItemRestoreHP_Party:: @@ -72,26 +77,19 @@ BattleScript_ItemRestoreHP_SendOutRevivedBattler: BattleScript_ItemCureStatus:: call BattleScript_UseItemMessage - itemcurestatus +BattleScript_ItemCureStatusAfterItemMsg: + itemcurestatus BattleScript_ItemCureStatusEnd updatestatusicon BS_SCRIPTING printstring STRINGID_ITEMCUREDSPECIESSTATUS waitmessage B_WAIT_TIME_LONG +BattleScript_ItemCureStatusEnd: end BattleScript_ItemHealAndCureStatus:: call BattleScript_UseItemMessage - itemrestorehp - itemcurestatus - printstring STRINGID_ITEMRESTOREDSPECIESHEALTH - waitmessage B_WAIT_TIME_LONG - bichalfword gMoveResultFlags, MOVE_RESULT_NO_EFFECT - orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE - healthbarupdate BS_SCRIPTING - datahpupdate BS_SCRIPTING - updatestatusicon BS_SCRIPTING - printstring STRINGID_ITEMRESTOREDSPECIESHEALTH - waitmessage B_WAIT_TIME_LONG - end + itemrestorehp BattleScript_ItemCureStatusAfterItemMsg + call BattleScript_ItemRestoreHPRet + goto BattleScript_ItemCureStatusAfterItemMsg BattleScript_ItemIncreaseStat:: call BattleScript_UseItemMessage @@ -118,7 +116,7 @@ BattleScript_ItemSetFocusEnergy:: setfocusenergy playmoveanimation BS_ATTACKER, MOVE_FOCUS_ENERGY waitanimation - copybyte sBATTLER, gBattlerAttacker + copybyte sBATTLER, gBattlerAttacker printstring STRINGID_PKMNUSEDXTOGETPUMPED waitmessage B_WAIT_TIME_LONG end diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index cb82e32176..606e5f0c93 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -15793,7 +15793,7 @@ void ApplyExperienceMultipliers(s32 *expAmount, u8 expGetterMonId, u8 faintedBat void BS_ItemRestoreHP(void) { - NATIVE_ARGS(); + NATIVE_ARGS(const u8 *alreadyMaxHpInstr); u16 healAmount; u32 battler = MAX_BATTLERS_COUNT; u32 healParam = GetItemEffect(gLastUsedItem)[6]; @@ -15803,90 +15803,113 @@ void BS_ItemRestoreHP(void) u16 maxHP = GetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_MAX_HP); gBattleCommunication[MULTIUSE_STATE] = 0; - // Track the number of Revives used in a battle. - if (hp == 0 && side == B_SIDE_PLAYER && gBattleResults.numRevivesUsed < 255) - gBattleResults.numRevivesUsed++; - - // Check if the recipient is an active battler. - if (gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[gBattlerAttacker]) - battler = gBattlerAttacker; - else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE - && gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)]) - battler = BATTLE_PARTNER(gBattlerAttacker); - - // Get amount to heal. - switch (healParam) + if (hp == maxHP) { - case ITEM6_HEAL_HP_FULL: - healAmount = maxHP; - break; - case ITEM6_HEAL_HP_HALF: - healAmount = maxHP / 2; - break; - case ITEM6_HEAL_HP_QUARTER: - healAmount = maxHP / 4; - break; - default: - healAmount = healParam; - break; - } - if (hp + healAmount > maxHP) - healAmount = maxHP - hp; - - gBattleScripting.battler = battler; - PREPARE_SPECIES_BUFFER(gBattleTextBuff1, GetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_SPECIES)); - - // Heal is applied as move damage if battler is active. - if (battler != MAX_BATTLERS_COUNT && hp != 0) - { - gBattleMoveDamage = -healAmount; - gBattlescriptCurrInstr = cmd->nextInstr; + gBattlescriptCurrInstr = cmd->alreadyMaxHpInstr; } else { - hp += healAmount; - SetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_HP, &hp); + // Track the number of Revives used in a battle. + if (hp == 0 && side == B_SIDE_PLAYER && gBattleResults.numRevivesUsed < 255) + gBattleResults.numRevivesUsed++; - // Revived battlers on the field need to be brought back. - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && battler != MAX_BATTLERS_COUNT) + // Check if the recipient is an active battler. + if (gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[gBattlerAttacker]) + battler = gBattlerAttacker; + else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE + && gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)]) + battler = BATTLE_PARTNER(gBattlerAttacker); + + // Get amount to heal. + switch (healParam) { - gAbsentBattlerFlags &= ~gBitTable[battler]; - gBattleCommunication[MULTIUSE_STATE] = TRUE; + case ITEM6_HEAL_HP_FULL: + healAmount = maxHP; + break; + case ITEM6_HEAL_HP_HALF: + healAmount = maxHP / 2; + break; + case ITEM6_HEAL_HP_QUARTER: + healAmount = maxHP / 4; + break; + default: + healAmount = healParam; + break; + } + if (hp + healAmount > maxHP) + healAmount = maxHP - hp; + + gBattleScripting.battler = battler; + PREPARE_SPECIES_BUFFER(gBattleTextBuff1, GetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_SPECIES)); + + // Heal is applied as move damage if battler is active. + if (battler != MAX_BATTLERS_COUNT && hp != 0) + { + gBattleMoveDamage = -healAmount; + gBattlescriptCurrInstr = cmd->nextInstr; + } + else + { + hp += healAmount; + SetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_HP, &hp); + + // Revived battlers on the field need to be brought back. + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && battler != MAX_BATTLERS_COUNT) + { + gAbsentBattlerFlags &= ~gBitTable[battler]; + gBattleCommunication[MULTIUSE_STATE] = TRUE; + } + gBattlescriptCurrInstr = BattleScript_ItemRestoreHP_Party; } - gBattlescriptCurrInstr = BattleScript_ItemRestoreHP_Party; } } void BS_ItemCureStatus(void) { - NATIVE_ARGS(); + NATIVE_ARGS(const u8 *noStatusInstr); u32 battler = gBattlerAttacker; u32 side = GetBattlerSide(gBattlerAttacker); + u32 previousStatus2 = 0; + bool32 statusChanged = FALSE; struct Pokemon *party = GetSideParty(side); // Heal Status2 conditions if battler is active. if (gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[gBattlerAttacker]) { + previousStatus2 = gBattleMons[battler].status2; gBattleMons[gBattlerAttacker].status2 &= ~GetItemStatus2Mask(gLastUsedItem); } else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)]) { - gBattleMons[gBattlerAttacker].status2 &= ~GetItemStatus2Mask(gLastUsedItem); battler = BATTLE_PARTNER(gBattlerAttacker); + previousStatus2 = gBattleMons[battler].status2; + gBattleMons[battler].status2 &= ~GetItemStatus2Mask(gLastUsedItem); } + if (previousStatus2 != gBattleMons[battler].status2) + statusChanged = TRUE; + // Heal Status1 conditions. - HealStatusConditions(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], GetItemStatus1Mask(gLastUsedItem), battler); + if (!HealStatusConditions(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], GetItemStatus1Mask(gLastUsedItem), battler)) + { + statusChanged = TRUE; + if (GetItemStatus1Mask(gLastUsedItem) & STATUS1_SLEEP) + gBattleMons[battler].status2 &= ~STATUS2_NIGHTMARE; + if (GetItemStatus2Mask(gLastUsedItem) & STATUS2_CONFUSION) + gStatuses4[battler] &= ~STATUS4_INFINITE_CONFUSION; + } - if (GetItemStatus1Mask(gLastUsedItem) & STATUS1_SLEEP) - gBattleMons[battler].status2 &= ~STATUS2_NIGHTMARE; - if (GetItemStatus2Mask(gLastUsedItem) & STATUS2_CONFUSION) - gStatuses4[battler] &= ~STATUS4_INFINITE_CONFUSION; - - gBattleScripting.battler = battler; - PREPARE_SPECIES_BUFFER(gBattleTextBuff1, GetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_SPECIES)); - gBattlescriptCurrInstr = cmd->nextInstr; + if (statusChanged) + { + gBattleScripting.battler = battler; + PREPARE_SPECIES_BUFFER(gBattleTextBuff1, GetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_SPECIES)); + gBattlescriptCurrInstr = cmd->nextInstr; + } + else + { + gBattlescriptCurrInstr = cmd->noStatusInstr; + } } void BS_ItemIncreaseStat(void) diff --git a/src/item.c b/src/item.c index 7c19b316d6..97b0f0c361 100644 --- a/src/item.c +++ b/src/item.c @@ -974,11 +974,11 @@ u32 GetItemStatus1Mask(u16 itemId) case ITEM3_BURN: return STATUS1_BURN; case ITEM3_POISON: - return STATUS1_POISON | STATUS1_TOXIC_POISON; + return STATUS1_PSN_ANY | STATUS1_TOXIC_COUNTER; case ITEM3_SLEEP: return STATUS1_SLEEP; case ITEM3_STATUS_ALL: - return STATUS1_ANY; + return STATUS1_ANY | STATUS1_TOXIC_COUNTER; } return 0; } diff --git a/test/battle/item_effect/cure_status.c b/test/battle/item_effect/cure_status.c index 02ba76bcc7..9efb0cbdf3 100644 --- a/test/battle/item_effect/cure_status.c +++ b/test/battle/item_effect/cure_status.c @@ -46,6 +46,24 @@ SINGLE_BATTLE_TEST("Antidote heals a battler from being badly poisoned") } } +SINGLE_BATTLE_TEST("Antidote resets Toxic Counter") +{ + GIVEN { + ASSUME(gItems[ITEM_ANTIDOTE].battleUsage == EFFECT_ITEM_CURE_STATUS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TOXIC); } + TURN { ; } + TURN { USE_ITEM(player, ITEM_ANTIDOTE, partyIndex: 0); } + } SCENE { + MESSAGE("Foe Wobbuffet used Toxic!"); + MESSAGE("Wobbuffet had its status healed!"); + } THEN { + EXPECT_EQ(player->status1, STATUS1_NONE); + } +} + SINGLE_BATTLE_TEST("Awakening heals a battler from being asleep") { GIVEN { diff --git a/test/battle/item_effect/heal_and_cure_status.c b/test/battle/item_effect/heal_and_cure_status.c index de6a459a18..4200261696 100644 --- a/test/battle/item_effect/heal_and_cure_status.c +++ b/test/battle/item_effect/heal_and_cure_status.c @@ -1,6 +1,11 @@ #include "global.h" #include "test/battle.h" +ASSUMPTIONS +{ + ASSUME(gItems[ITEM_FULL_RESTORE].battleUsage == EFFECT_ITEM_HEAL_AND_CURE_STATUS); +} + SINGLE_BATTLE_TEST("Full Restore restores a battler's HP and cures any primary status") { u16 status; @@ -10,24 +15,48 @@ SINGLE_BATTLE_TEST("Full Restore restores a battler's HP and cures any primary s PARAMETRIZE{ status = STATUS1_POISON; } PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE{ status = STATUS1_NONE; } GIVEN { - ASSUME(gItems[ITEM_FULL_RESTORE].battleUsage == EFFECT_ITEM_HEAL_AND_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(300); Status1(status); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN{ USE_ITEM(player, ITEM_FULL_RESTORE, partyIndex: 0); } } SCENE { MESSAGE("Wobbuffet had its HP restored!"); + if (status != STATUS1_NONE) { + MESSAGE("Wobbuffet had its status healed!"); // The message is not printed if status wasn't healed. + } } THEN { EXPECT_EQ(player->hp, player->maxHP); EXPECT_EQ(player->status1, STATUS1_NONE); } } +SINGLE_BATTLE_TEST("Full Restore heals a battler from any primary status") +{ + u16 status; + PARAMETRIZE{ status = STATUS1_BURN; } + PARAMETRIZE{ status = STATUS1_FREEZE; } + PARAMETRIZE{ status = STATUS1_PARALYSIS; } + PARAMETRIZE{ status = STATUS1_POISON; } + PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } + PARAMETRIZE{ status = STATUS1_SLEEP; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Status1(status); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { USE_ITEM(player, ITEM_FULL_RESTORE, partyIndex: 0); } + } SCENE { + NOT MESSAGE("Wobbuffet had its HP restored!"); // The message is not printed if mon has max HP. + MESSAGE("Wobbuffet had its status healed!"); + } THEN { + EXPECT_EQ(player->status1, STATUS1_NONE); + } +} + SINGLE_BATTLE_TEST("Full Restore restores a battler's HP and cures confusion") { GIVEN { - ASSUME(gItems[ITEM_FULL_RESTORE].battleUsage == EFFECT_ITEM_HEAL_AND_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(300); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -41,3 +70,21 @@ SINGLE_BATTLE_TEST("Full Restore restores a battler's HP and cures confusion") EXPECT_EQ(player->hp, player->maxHP); } } + +SINGLE_BATTLE_TEST("Full Restore resets Toxic Counter") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TOXIC); } + TURN { ; } + TURN { USE_ITEM(player, ITEM_FULL_RESTORE, partyIndex: 0); } + } SCENE { + MESSAGE("Foe Wobbuffet used Toxic!"); + MESSAGE("Wobbuffet had its HP restored!"); + MESSAGE("Wobbuffet had its status healed!"); + } THEN { + EXPECT_EQ(player->status1, STATUS1_NONE); + } +} From e89f8e00ed1bc32596bc98aba6534486adc8ed9f Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 9 Feb 2024 14:00:42 +0100 Subject: [PATCH 2/3] =?UTF-8?q?Fixes=20Hit=20Escape=20moves=20interaction?= =?UTF-8?q?=20with=20hold=20effects=20and=20switch=20in=20ab=E2=80=A6=20(#?= =?UTF-8?q?4091)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixes Hit Escape moves interaction with hold effects and switch in abilities * leftover * fix spelling * fix desc. --- README.md | 2 +- data/battle_scripts_1.s | 2 + include/battle.h | 1 - include/battle_script_commands.h | 2 +- src/battle_script_commands.c | 43 +++++--------- src/battle_util.c | 18 +----- test/battle/ability/download.c | 10 ++-- test/battle/ability/intimidate.c | 45 +++++++++++---- test/battle/form_change/primal_reversion.c | 20 +++++++ test/battle/move_effect/hit_escape.c | 66 ++++++++++++++++++++++ 10 files changed, 146 insertions(+), 63 deletions(-) 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); + } +} From 47abc33c84f5b374d9513245d6c8a41f534378b7 Mon Sep 17 00:00:00 2001 From: Hungry Pickle <81360291+HungryPickle@users.noreply.github.com> Date: Fri, 9 Feb 2024 09:35:40 -0500 Subject: [PATCH 3/3] Fixes Stench ability triggering on non-damaging attacks (#4159) * Fixes Stench ability triggering on non-damaging attacks * adds stench ability test * added stench ability test for partner pokemon --------- Co-authored-by: HungryPickle --- src/battle_util.c | 2 +- test/battle/ability/stench.c | 48 ++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/battle_util.c b/src/battle_util.c index 0674329c3a..cbc5898f91 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5792,7 +5792,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && gBattleMons[gBattlerTarget].hp != 0 && !gProtectStructs[gBattlerAttacker].confusionSelfDmg && RandomWeighted(RNG_STENCH, 9, 1) - && !IS_MOVE_STATUS(move) + && TARGET_TURN_DAMAGED && gBattleMoves[gCurrentMove].effect != EFFECT_FLINCH_HIT && gBattleMoves[gCurrentMove].effect != EFFECT_FLINCH_STATUS && gBattleMoves[gCurrentMove].effect != EFFECT_TRIPLE_ARROWS) diff --git a/test/battle/ability/stench.c b/test/battle/ability/stench.c index 4153e6b155..62944a2f4d 100644 --- a/test/battle/ability/stench.c +++ b/test/battle/ability/stench.c @@ -31,4 +31,52 @@ SINGLE_BATTLE_TEST("Stench does not stack with King's Rock") } } +DOUBLE_BATTLE_TEST("Stench only triggers if target takes damage") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_TACKLE].power > 0); + ASSUME(gBattleMoves[MOVE_FAKE_OUT].effect == EFFECT_FAKE_OUT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GRIMER) { Ability(ABILITY_STENCH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_FAKE_OUT, target: opponentLeft); + MOVE(opponentLeft, MOVE_TACKLE, WITH_RNG(RNG_STENCH, TRUE), target: playerRight); + MOVE(playerRight, MOVE_TACKLE, target: opponentRight); + } + TURN { + MOVE(opponentLeft, MOVE_SCARY_FACE, WITH_RNG(RNG_STENCH, TRUE), target: playerRight); + MOVE(playerRight, MOVE_TACKLE, target: opponentRight); + } + } SCENE { + NONE_OF { MESSAGE("Wynaut flinched!"); } + } +} + +DOUBLE_BATTLE_TEST("Stench doesn't trigger if partner uses a move") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_TACKLE].power > 0); + ASSUME(gBattleMoves[MOVE_FAKE_OUT].effect == EFFECT_FAKE_OUT); + PLAYER(SPECIES_WOBBUFFET) { Speed(20); } + PLAYER(SPECIES_WYNAUT) { Speed(10); } + OPPONENT(SPECIES_GRIMER) { Speed(100); Ability(ABILITY_STENCH); } + OPPONENT(SPECIES_WOBBUFFET) {Speed(50); } + } WHEN { + TURN { + MOVE(playerLeft, MOVE_FAKE_OUT, target: opponentLeft); + MOVE(opponentRight, MOVE_TACKLE, target: playerRight); + MOVE(playerRight, MOVE_TACKLE, target: opponentRight); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, playerLeft); + MESSAGE("Foe Grimer flinched!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentRight); + NOT MESSAGE("Wynaut flinched!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight); + } +} + // TODO: Test against interaction with multi hits