diff --git a/include/battle.h b/include/battle.h index 9a627b16b0..1996ee0757 100644 --- a/include/battle.h +++ b/include/battle.h @@ -146,7 +146,7 @@ struct ProtectStruct u32 stealMove:1; u32 prlzImmobility:1; u32 confusionSelfDmg:1; - u32 targetAffected:1; + u32 touchedProtectLike:1; u32 chargingTurn:1; u32 fleeType:2; // 0: Normal, 1: FLEE_ITEM, 2: FLEE_ABILITY u32 usedImprisonedMove:1; @@ -164,7 +164,6 @@ struct ProtectStruct u32 statRaised:1; u32 usedCustapBerry:1; // also quick claw // End of 32-bit bitfield - u16 touchedProtectLike:1; u16 disableEjectPack:1; u16 statFell:1; u16 pranksterElevated:1; @@ -175,7 +174,7 @@ struct ProtectStruct u16 eatMirrorHerb:1; u16 activateOpportunist:2; // 2 - to copy stats. 1 - stats copied (do not repeat). 0 - no stats to copy u16 usedAllySwitch:1; - u16 padding:4; + u16 padding:5; // End of 16-bit bitfield u16 physicalDmg; u16 specialDmg; diff --git a/src/battle_main.c b/src/battle_main.c index 40894346ce..bbb164332b 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3286,7 +3286,6 @@ const u8* FaintClearSetData(u32 battler) gProtectStructs[battler].stealMove = FALSE; gProtectStructs[battler].prlzImmobility = FALSE; gProtectStructs[battler].confusionSelfDmg = FALSE; - gProtectStructs[battler].targetAffected = FALSE; gProtectStructs[battler].chargingTurn = FALSE; gProtectStructs[battler].fleeType = 0; gProtectStructs[battler].usedImprisonedMove = FALSE; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 3a90d27b09..4dd66975b6 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1155,12 +1155,11 @@ bool32 IsMoveNotAllowedInSkyBattles(u32 move) u32 NumAffectedSpreadMoveTargets(void) { - u32 targetCount = 1; + u32 targetCount = 0; if (!IsDoubleSpreadMove()) return targetCount; - targetCount = 0; for (u32 battler = 0; battler < gBattlersCount; battler++) { if (!(gBattleStruct->moveResultFlags[battler] & MOVE_RESULT_NO_EFFECT)) @@ -7113,10 +7112,6 @@ static void Cmd_moveend(void) case MOVEEND_NEXT_TARGET: // For moves hitting two opposing Pokemon. { u16 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); - // Set a flag if move hits either target (for throat spray that can't check damage) - if (!(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) - && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) - gProtectStructs[gBattlerAttacker].targetAffected = TRUE; gBattleStruct->battlerState[gBattlerAttacker].targetsDone[gBattlerTarget] = TRUE; if (!(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) @@ -7566,7 +7561,6 @@ static void Cmd_moveend(void) DebugPrintfLevel(MGBA_LOG_WARN, "savedTargetCount is greater than 0! More calls to SaveBattlerTarget than RestoreBattlerTarget!"); // #endif } - gProtectStructs[gBattlerAttacker].targetAffected = FALSE; gProtectStructs[gBattlerAttacker].shellTrap = FALSE; gBattleStruct->ateBoost[gBattlerAttacker] = FALSE; gStatuses3[gBattlerAttacker] &= ~STATUS3_ME_FIRST; diff --git a/src/battle_util.c b/src/battle_util.c index e2f7470c44..6301d1836a 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -65,6 +65,7 @@ static u32 GetFlingPowerFromItemId(u32 itemId); static void SetRandomMultiHitCounter(); static u32 GetBattlerItemHoldEffectParam(u32 battler, u32 item); static bool32 CanBeInfinitelyConfused(u32 battler); +static bool32 IsAnyTargetAffected(u32 battlerAtk); ARM_FUNC NOINLINE static uq4_12_t PercentToUQ4_12(u32 percent); ARM_FUNC NOINLINE static uq4_12_t PercentToUQ4_12_Floored(u32 percent); @@ -4801,8 +4802,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 break; case ABILITY_IMPOSTER: { - u32 diagonalBattler = BATTLE_OPPOSITE(battler); - if (IsDoubleBattle()) + u32 diagonalBattler = BATTLE_OPPOSITE(battler); + if (IsDoubleBattle()) diagonalBattler = BATTLE_PARTNER(diagonalBattler); if (IsBattlerAlive(diagonalBattler) && !(gBattleMons[diagonalBattler].status2 & (STATUS2_TRANSFORMED | STATUS2_SUBSTITUTE)) @@ -8213,9 +8214,9 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler, bool32 moveTurn) } break; case HOLD_EFFECT_THROAT_SPRAY: // Does NOT need to be a damaging move - if (gProtectStructs[gBattlerAttacker].targetAffected + if (IsSoundMove(gCurrentMove) && IsBattlerAlive(gBattlerAttacker) - && IsSoundMove(gCurrentMove) + && IsAnyTargetAffected(gBattlerAttacker) && CompareStat(gBattlerAttacker, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN) && !NoAliveMonsForEitherParty()) // Don't activate if battle will end { @@ -12408,3 +12409,16 @@ bool32 HasWeatherEffect(void) return TRUE; } + +static bool32 IsAnyTargetAffected(u32 battlerAtk) +{ + for (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++) + { + if (battlerAtk == battlerDef) + continue; + + if (!(gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NO_EFFECT)) + return TRUE; + } + return FALSE; +} diff --git a/test/battle/hold_effect/throat_spray.c b/test/battle/hold_effect/throat_spray.c new file mode 100644 index 0000000000..2c74387158 --- /dev/null +++ b/test/battle/hold_effect/throat_spray.c @@ -0,0 +1,90 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(ItemId_GetHoldEffect(ITEM_THROAT_SPRAY) == HOLD_EFFECT_THROAT_SPRAY); + ASSUME(IsSoundMove(MOVE_HYPER_VOICE) == TRUE); +} + +DOUBLE_BATTLE_TEST("Throat Spray activates after both hits of a spread move") +{ + s16 firstHit, secondHit; + + GIVEN { + ASSUME(GetMoveTarget(MOVE_HYPER_VOICE) == MOVE_TARGET_BOTH); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_THROAT_SPRAY); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_HYPER_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &firstHit); + HP_BAR(opponentRight, captureDamage: &secondHit); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + } THEN { + EXPECT_EQ(firstHit, secondHit); + } +} + +SINGLE_BATTLE_TEST("Throat Spray increases Sp. Atk by one stage") +{ + s16 normalHit; + s16 boostedHit; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_THROAT_SPRAY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HYPER_VOICE); } + TURN { MOVE(player, MOVE_HYPER_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, player); + HP_BAR(opponent, captureDamage: &normalHit); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, player); + HP_BAR(opponent, captureDamage: &boostedHit); + } THEN { + EXPECT_MUL_EQ(normalHit, Q_4_12(1.5), boostedHit); + } +} + +SINGLE_BATTLE_TEST("Throat Spray activates when a sound move is used") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SWIFT; } + PARAMETRIZE { move = MOVE_HYPER_VOICE; } + + GIVEN { + ASSUME(IsSoundMove(MOVE_SWIFT) != IsSoundMove(MOVE_HYPER_VOICE)); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_THROAT_SPRAY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + if (move == MOVE_HYPER_VOICE) + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + else + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } +} + +SINGLE_BATTLE_TEST("Throat Spray does not activate if move fails") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_THROAT_SPRAY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_HYPER_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } + } +}