diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 71df1f7ddebd..748fa2c5bee5 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -1034,6 +1034,7 @@ BattleScript_EffectCoaching:: setallytonexttarget EffectCoaching_CheckAllyStats goto BattleScript_ButItFailed EffectCoaching_CheckAllyStats: + accuracycheck BattleScript_ButItFailed, NO_ACC_CALC_CHECK_LOCK_ON jumpifstat BS_TARGET, CMP_NOT_EQUAL, STAT_ATK, MAX_STAT_STAGE, BattleScript_CoachingWorks jumpifstat BS_TARGET, CMP_NOT_EQUAL, STAT_DEF, MAX_STAT_STAGE, BattleScript_CoachingWorks goto BattleScript_ButItFailed @ ally at max atk, def @@ -3776,6 +3777,7 @@ BattleScript_TwoTurnMovesSecondTurn:: BattleScript_TwoTurnMovesSecondTurnRet: setbyte sB_ANIM_TURN, 1 + setbyte sB_ANIM_TARGETS_HIT, 0 clearstatusfromeffect BS_ATTACKER, MOVE_EFFECT_CHARGING clearsemiinvulnerablebit @ only for moves with EFFECT_SEMI_INVULNERABLE/EFFECT_SKY_DROP return diff --git a/docs/team_procedures/scope.md b/docs/team_procedures/scope.md index e3c736c1cce7..3293cea6dceb 100644 --- a/docs/team_procedures/scope.md +++ b/docs/team_procedures/scope.md @@ -54,6 +54,7 @@ Pull Requests that fall into this category are not in scope by default and shoul 2. **Fangame Features**: Adds a popular feature from other fangames 3. **Popular Non-SS Features**: Exceptions can be made for uniquely popular or requested features (Drowsy, PLA Legend Plate, etc.) 4. **External Program**: External programs like poryscript, porymoves, etc. +5. **Intergenerational Feature Compatibility**: Addresses limitations and issues resulting from including all generational behaviours in a GBA native title, and extrapolation of features no longer supported by GameFreak ## Workflow for Proposed Feature Scope Discussion For the contributor: diff --git a/graphics/battle_interface/alpha_indicator.png b/graphics/battle_interface/alpha_indicator.png index 84cfee2cf136..bfa504e89a79 100644 Binary files a/graphics/battle_interface/alpha_indicator.png and b/graphics/battle_interface/alpha_indicator.png differ diff --git a/graphics/battle_interface/bug_indicator.png b/graphics/battle_interface/bug_indicator.png index 7ab7dc6dfa37..2e5893ead84d 100644 Binary files a/graphics/battle_interface/bug_indicator.png and b/graphics/battle_interface/bug_indicator.png differ diff --git a/graphics/battle_interface/dark_indicator.png b/graphics/battle_interface/dark_indicator.png index 149d2155a397..f7b6d8e070e2 100644 Binary files a/graphics/battle_interface/dark_indicator.png and b/graphics/battle_interface/dark_indicator.png differ diff --git a/graphics/battle_interface/dragon_indicator.png b/graphics/battle_interface/dragon_indicator.png index 4c1d9e1f785a..55937688e579 100644 Binary files a/graphics/battle_interface/dragon_indicator.png and b/graphics/battle_interface/dragon_indicator.png differ diff --git a/graphics/battle_interface/dynamax_indicator.png b/graphics/battle_interface/dynamax_indicator.png index ebfbe86da62b..736893c28d9f 100644 Binary files a/graphics/battle_interface/dynamax_indicator.png and b/graphics/battle_interface/dynamax_indicator.png differ diff --git a/graphics/battle_interface/electric_indicator.png b/graphics/battle_interface/electric_indicator.png index 518907dc05fc..b96c7d630a53 100644 Binary files a/graphics/battle_interface/electric_indicator.png and b/graphics/battle_interface/electric_indicator.png differ diff --git a/graphics/battle_interface/fairy_indicator.png b/graphics/battle_interface/fairy_indicator.png index a41736eeec85..bdfc692c4397 100644 Binary files a/graphics/battle_interface/fairy_indicator.png and b/graphics/battle_interface/fairy_indicator.png differ diff --git a/graphics/battle_interface/fighting_indicator.png b/graphics/battle_interface/fighting_indicator.png index 6102ab68465b..a64a768aaabf 100644 Binary files a/graphics/battle_interface/fighting_indicator.png and b/graphics/battle_interface/fighting_indicator.png differ diff --git a/graphics/battle_interface/fire_indicator.png b/graphics/battle_interface/fire_indicator.png index 4d3e59d543ac..bffb030804c2 100644 Binary files a/graphics/battle_interface/fire_indicator.png and b/graphics/battle_interface/fire_indicator.png differ diff --git a/graphics/battle_interface/flying_indicator.png b/graphics/battle_interface/flying_indicator.png index 767954b70633..62d167efa6d1 100644 Binary files a/graphics/battle_interface/flying_indicator.png and b/graphics/battle_interface/flying_indicator.png differ diff --git a/graphics/battle_interface/ghost_indicator.png b/graphics/battle_interface/ghost_indicator.png index c1aeec6059ff..76a946dfab7c 100644 Binary files a/graphics/battle_interface/ghost_indicator.png and b/graphics/battle_interface/ghost_indicator.png differ diff --git a/graphics/battle_interface/grass_indicator.png b/graphics/battle_interface/grass_indicator.png index 81fa2589d0da..78f6f7aeada3 100644 Binary files a/graphics/battle_interface/grass_indicator.png and b/graphics/battle_interface/grass_indicator.png differ diff --git a/graphics/battle_interface/ground_indicator.png b/graphics/battle_interface/ground_indicator.png index 7a306510fe75..8c716aefa7fe 100644 Binary files a/graphics/battle_interface/ground_indicator.png and b/graphics/battle_interface/ground_indicator.png differ diff --git a/graphics/battle_interface/ice_indicator.png b/graphics/battle_interface/ice_indicator.png index a40d96f01831..c7d7062eb8b7 100644 Binary files a/graphics/battle_interface/ice_indicator.png and b/graphics/battle_interface/ice_indicator.png differ diff --git a/graphics/battle_interface/mega_indicator.png b/graphics/battle_interface/mega_indicator.png index 93e113ded4ef..62fcddd65808 100644 Binary files a/graphics/battle_interface/mega_indicator.png and b/graphics/battle_interface/mega_indicator.png differ diff --git a/graphics/battle_interface/normal_indicator.png b/graphics/battle_interface/normal_indicator.png index 029827e2962b..8876bc992226 100644 Binary files a/graphics/battle_interface/normal_indicator.png and b/graphics/battle_interface/normal_indicator.png differ diff --git a/graphics/battle_interface/omega_indicator.png b/graphics/battle_interface/omega_indicator.png index 8e21007a800b..f491251c50a6 100644 Binary files a/graphics/battle_interface/omega_indicator.png and b/graphics/battle_interface/omega_indicator.png differ diff --git a/graphics/battle_interface/poison_indicator.png b/graphics/battle_interface/poison_indicator.png index c2646d0a71b5..10e6ba45f51d 100644 Binary files a/graphics/battle_interface/poison_indicator.png and b/graphics/battle_interface/poison_indicator.png differ diff --git a/graphics/battle_interface/psychic_indicator.png b/graphics/battle_interface/psychic_indicator.png index f70352d2ee8f..4f3dec93195a 100644 Binary files a/graphics/battle_interface/psychic_indicator.png and b/graphics/battle_interface/psychic_indicator.png differ diff --git a/graphics/battle_interface/rock_indicator.png b/graphics/battle_interface/rock_indicator.png index 0329ec678cd1..3f92eb296ede 100644 Binary files a/graphics/battle_interface/rock_indicator.png and b/graphics/battle_interface/rock_indicator.png differ diff --git a/graphics/battle_interface/steel_indicator.png b/graphics/battle_interface/steel_indicator.png index e84d9fa1b0c5..682d738c1a7d 100644 Binary files a/graphics/battle_interface/steel_indicator.png and b/graphics/battle_interface/steel_indicator.png differ diff --git a/graphics/battle_interface/stellar_indicator.png b/graphics/battle_interface/stellar_indicator.png index 7551e2b2305a..202131c1a2de 100644 Binary files a/graphics/battle_interface/stellar_indicator.png and b/graphics/battle_interface/stellar_indicator.png differ diff --git a/graphics/battle_interface/water_indicator.png b/graphics/battle_interface/water_indicator.png index bdeb401f4abe..c7d189479371 100644 Binary files a/graphics/battle_interface/water_indicator.png and b/graphics/battle_interface/water_indicator.png differ diff --git a/include/battle.h b/include/battle.h index ecad976d5e6d..27227544d9e7 100644 --- a/include/battle.h +++ b/include/battle.h @@ -829,7 +829,10 @@ struct BattleStruct u8 calculatedSpreadMoveAccuracy:1; u8 printedStrongWindsWeakenedAttack:1; u8 numSpreadTargets:2; - u8 padding3:2; + u8 bypassMoldBreakerChecks:1; // for ABILITYEFFECT_IMMUNITY + u8 padding3:1; + u8 usedEjectItem; + u8 usedMicleBerry; struct MessageStatus slideMessageStatus; u8 trainerSlideSpriteIds[MAX_BATTLERS_COUNT]; u8 embodyAspectBoost[NUM_BATTLE_SIDES]; diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index ba6e7cd69cc5..4855184facae 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -218,6 +218,7 @@ bool32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData void IncreaseTidyUpScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); bool32 AI_ShouldSpicyExtract(u32 battlerAtk, u32 battlerAtkPartner, u32 move, struct AiLogicData *aiData); u32 IncreaseSubstituteMoveScore(u32 battlerAtk, u32 battlerDef, u32 move); +bool32 IsBattlerItemEnabled(u32 battler); bool32 IsBattlerPredictedToSwitch(u32 battler); bool32 HasLowAccuracyMove(u32 battlerAtk, u32 battlerDef); diff --git a/include/constants/battle.h b/include/constants/battle.h index 66927bf29460..ce1cf0e9487c 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -558,7 +558,7 @@ enum MoveEffects #define MOVE_TARGET_SELECTED 0 #define MOVE_TARGET_DEPENDS (1 << 0) -#define MOVE_TARGET_USER_OR_SELECTED (1 << 1) +#define MOVE_TARGET_OPPONENT (1 << 1) #define MOVE_TARGET_RANDOM (1 << 2) #define MOVE_TARGET_BOTH (1 << 3) #define MOVE_TARGET_USER (1 << 4) diff --git a/include/constants/event_objects.h b/include/constants/event_objects.h index 306aa055aad5..db8d96cae484 100644 --- a/include/constants/event_objects.h +++ b/include/constants/event_objects.h @@ -270,10 +270,10 @@ #define OBJ_EVENT_GFX_VAR_E (OBJ_EVENT_GFX_VARS + 0xE) #define OBJ_EVENT_GFX_VAR_F (OBJ_EVENT_GFX_VARS + 0xF) -#define OBJ_EVENT_MON (1u << 15) -#define OBJ_EVENT_MON_SHINY (1u << 14) -#define OBJ_EVENT_MON_FEMALE (1u << 13) -#define OBJ_EVENT_MON_SPECIES_MASK (~(7u << 13)) +#define OBJ_EVENT_MON (1u << 14) +#define OBJ_EVENT_MON_SHINY (1u << 13) +#define OBJ_EVENT_MON_FEMALE (1u << 12) +#define OBJ_EVENT_MON_SPECIES_MASK (~(7u << 12)) // Used to call a specific species' follower graphics. Useful for static encounters. #define OBJ_EVENT_GFX_SPECIES(name) (SPECIES_##name + OBJ_EVENT_MON) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index e3d0dafbe51e..6244245d82ec 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -2349,9 +2349,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; case EFFECT_NATURAL_GIFT: - if (aiData->abilities[battlerAtk] == ABILITY_KLUTZ - || gFieldStatuses & STATUS_FIELD_MAGIC_ROOM - || GetPocketByItemId(gBattleMons[battlerAtk].item) != POCKET_BERRIES) + if (!IsBattlerItemEnabled(battlerAtk) || GetPocketByItemId(gBattleMons[battlerAtk].item) != POCKET_BERRIES) ADJUST_SCORE(-10); break; case EFFECT_GRASSY_TERRAIN: @@ -2458,8 +2456,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; case EFFECT_EMBARGO: - if (aiData->abilities[battlerDef] == ABILITY_KLUTZ - || gFieldStatuses & STATUS_FIELD_MAGIC_ROOM + if (!IsBattlerItemEnabled(battlerAtk) || gDisableStructs[battlerDef].embargoTimer != 0 || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); @@ -2706,7 +2703,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } // move effect checks // Choice items - if (HOLD_EFFECT_CHOICE(aiData->holdEffects[battlerAtk]) && gBattleMons[battlerAtk].ability != ABILITY_KLUTZ) + if (HOLD_EFFECT_CHOICE(aiData->holdEffects[battlerAtk]) && IsBattlerItemEnabled(battlerAtk)) { // Don't use user-target moves ie. Swords Dance, with exceptions if ((moveTarget & MOVE_TARGET_USER) @@ -3369,6 +3366,12 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT && effectiveness >= UQ_4_12(1.0)) ADJUST_SCORE(DECENT_EFFECT); break; + case EFFECT_DREAM_EATER: + case EFFECT_STRENGTH_SAP: + case EFFECT_AQUA_RING: + if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT) + ADJUST_SCORE(DECENT_EFFECT); + break; case EFFECT_EXPLOSION: case EFFECT_MEMENTO: if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_WILL_SUICIDE && gBattleMons[battlerDef].statStages[STAT_EVASION] < 7) @@ -3590,8 +3593,6 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_MOONLIGHT: if (ShouldRecover(battlerAtk, battlerDef, move, 50)) ADJUST_SCORE(GOOD_EFFECT); - if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT) - ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_TOXIC: case EFFECT_POISON: @@ -3667,7 +3668,8 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) || aiData->abilities[battlerDef] == ABILITY_MAGIC_GUARD) break; ADJUST_SCORE(GOOD_EFFECT); - if (!HasDamagingMove(battlerDef) || IsBattlerTrapped(battlerDef, FALSE)) + if (!HasDamagingMove(battlerDef) || IsBattlerTrapped(battlerDef, FALSE) + || aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT) ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_DO_NOTHING: diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 663e9fb6330a..ef8f45fb179f 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -966,7 +966,7 @@ static bool32 ShouldSwitchIfBadChoiceLock(u32 battler) { u32 holdEffect = GetBattlerHoldEffect(battler, FALSE); - if (HOLD_EFFECT_CHOICE(holdEffect) && gBattleMons[battler].ability != ABILITY_KLUTZ) + if (HOLD_EFFECT_CHOICE(holdEffect) && IsBattlerItemEnabled(battler)) { if (GetMoveCategory(AI_DATA->lastUsedMove[battler]) == DAMAGE_CATEGORY_STATUS && RandomPercentage(RNG_AI_SWITCH_CHOICE_LOCKED, GetSwitchChance(SHOULD_SWITCH_CHOICE_LOCKED))) return SetSwitchinAndSwitch(battler, PARTY_SIZE); @@ -1379,10 +1379,10 @@ bool32 IsMonGrounded(u16 heldItemEffect, u32 ability, u8 type1, u8 type2) { // List that makes mon not grounded if (type1 == TYPE_FLYING || type2 == TYPE_FLYING || ability == ABILITY_LEVITATE - || (heldItemEffect == HOLD_EFFECT_AIR_BALLOON && ability != ABILITY_KLUTZ)) + || (heldItemEffect == HOLD_EFFECT_AIR_BALLOON && !(ability == ABILITY_KLUTZ || (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM)))) { // List that overrides being off the ground - if ((heldItemEffect == HOLD_EFFECT_IRON_BALL && ability != ABILITY_KLUTZ) || (gFieldStatuses & STATUS_FIELD_GRAVITY) || (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM)) + if ((heldItemEffect == HOLD_EFFECT_IRON_BALL && !(ability == ABILITY_KLUTZ || (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM))) || (gFieldStatuses & STATUS_FIELD_GRAVITY) || (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM)) return TRUE; else return FALSE; diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 57fcc0b7acba..f7b5ab9c64c0 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -423,15 +423,6 @@ static inline s32 DmgRoll(s32 dmg) bool32 IsDamageMoveUnusable(u32 battlerAtk, u32 battlerDef, u32 move, u32 moveType) { struct AiLogicData *aiData = AI_DATA; - u32 battlerDefAbility; - - if (DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move)) - battlerDefAbility = ABILITY_NONE; - else - battlerDefAbility = aiData->abilities[battlerDef]; - - if (battlerDef == BATTLE_PARTNER(battlerAtk)) - battlerDefAbility = aiData->abilities[battlerDef]; if (gBattleStruct->battlerState[battlerDef].commandingDondozo) return TRUE; @@ -470,7 +461,7 @@ bool32 IsDamageMoveUnusable(u32 battlerAtk, u32 battlerDef, u32 move, u32 moveTy return TRUE; break; case EFFECT_POLTERGEIST: - if (AI_DATA->items[battlerDef] == ITEM_NONE || gFieldStatuses & STATUS_FIELD_MAGIC_ROOM || battlerDefAbility == ABILITY_KLUTZ) + if (AI_DATA->items[battlerDef] == ITEM_NONE || !IsBattlerItemEnabled(battlerDef)) return TRUE; break; case EFFECT_FIRST_TURN_ONLY: @@ -4296,3 +4287,16 @@ bool32 HasLowAccuracyMove(u32 battlerAtk, u32 battlerDef) } return FALSE; } + +bool32 IsBattlerItemEnabled(u32 battler) +{ + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_NEGATE_UNAWARE) + return TRUE; + if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) + return FALSE; + if (gStatuses3[battler] & STATUS3_EMBARGO) + return FALSE; + if (gBattleMons[battler].ability == ABILITY_KLUTZ && !(gStatuses3[battler] & STATUS3_GASTRO_ACID)) + return FALSE; + return TRUE; +} diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c index 28e572d1a506..38fb304ac1de 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -560,7 +560,7 @@ static void OpponentHandleChooseMove(u32 battler) default: { u16 chosenMove = moveInfo->moves[chosenMoveId]; - if (GetBattlerMoveTargetType(battler, chosenMove) & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) + if (GetBattlerMoveTargetType(battler, chosenMove) & MOVE_TARGET_USER) gBattlerTarget = battler; if (GetBattlerMoveTargetType(battler, chosenMove) & MOVE_TARGET_BOTH) { @@ -595,7 +595,7 @@ static void OpponentHandleChooseMove(u32 battler) move = moveInfo->moves[chosenMoveId]; } while (move == MOVE_NONE); - if (GetBattlerMoveTargetType(battler, move) & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) + if (GetBattlerMoveTargetType(battler, move) & MOVE_TARGET_USER) BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (battler << 8)); else if (IsDoubleBattle()) { diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index fee5c963e251..5bac769eb60b 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -495,8 +495,6 @@ void HandleInputChooseTarget(u32 battler) case B_POSITION_PLAYER_RIGHT: if (battler != gMultiUsePlayerCursor) i++; - else if (moveTarget & MOVE_TARGET_USER_OR_SELECTED) - i++; break; case B_POSITION_OPPONENT_LEFT: case B_POSITION_OPPONENT_RIGHT: @@ -505,7 +503,8 @@ void HandleInputChooseTarget(u32 battler) } if (gAbsentBattlerFlags & (1u << gMultiUsePlayerCursor) - || !CanTargetBattler(battler, gMultiUsePlayerCursor, move)) + || !CanTargetBattler(battler, gMultiUsePlayerCursor, move) + || (moveTarget & MOVE_TARGET_OPPONENT && GetBattlerSide(gMultiUsePlayerCursor) == B_SIDE_PLAYER)) i = 0; } while (i == 0); } @@ -545,8 +544,6 @@ void HandleInputChooseTarget(u32 battler) case B_POSITION_PLAYER_RIGHT: if (battler != gMultiUsePlayerCursor) i++; - else if (moveTarget & MOVE_TARGET_USER_OR_SELECTED) - i++; break; case B_POSITION_OPPONENT_LEFT: case B_POSITION_OPPONENT_RIGHT: @@ -555,7 +552,8 @@ void HandleInputChooseTarget(u32 battler) } if (gAbsentBattlerFlags & (1u << gMultiUsePlayerCursor) - || !CanTargetBattler(battler, gMultiUsePlayerCursor, move)) + || !CanTargetBattler(battler, gMultiUsePlayerCursor, move) + || (moveTarget & MOVE_TARGET_OPPONENT && GetBattlerSide(gMultiUsePlayerCursor) == B_SIDE_PLAYER)) i = 0; } while (i == 0); } @@ -690,12 +688,7 @@ void HandleInputChooseMove(u32 battler) else gMultiUsePlayerCursor = GetOpposingSideBattler(battler); - if (!gBattleResources->bufferA[battler][1]) // not a double battle - { - if (moveTarget & MOVE_TARGET_USER_OR_SELECTED && !gBattleResources->bufferA[battler][2]) - canSelectTarget = 1; - } - else // double battle + if (gBattleResources->bufferA[battler][1]) // a double battle { if (!(moveTarget & (MOVE_TARGET_RANDOM | MOVE_TARGET_BOTH | MOVE_TARGET_DEPENDS | MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_OPPONENTS_FIELD | MOVE_TARGET_USER | MOVE_TARGET_ALLY))) canSelectTarget = 1; // either selected or user @@ -706,7 +699,7 @@ void HandleInputChooseMove(u32 battler) { canSelectTarget = 0; } - else if (!(moveTarget & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED)) && CountAliveMonsInBattle(BATTLE_ALIVE_EXCEPT_BATTLER, battler) <= 1) + else if (!(moveTarget & MOVE_TARGET_USER) && CountAliveMonsInBattle(BATTLE_ALIVE_EXCEPT_BATTLER, battler) <= 1) { gMultiUsePlayerCursor = GetDefaultMoveTarget(battler); canSelectTarget = 0; @@ -749,7 +742,7 @@ void HandleInputChooseMove(u32 battler) case 1: gBattlerControllerFuncs[battler] = HandleInputChooseTarget; - if (moveTarget & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED)) + if (moveTarget & MOVE_TARGET_USER) gMultiUsePlayerCursor = battler; else if (gAbsentBattlerFlags & (1u << GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT))) gMultiUsePlayerCursor = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); diff --git a/src/battle_controller_player_partner.c b/src/battle_controller_player_partner.c index a57bb8e2a52e..39c1d2552651 100644 --- a/src/battle_controller_player_partner.c +++ b/src/battle_controller_player_partner.c @@ -353,7 +353,7 @@ static void PlayerPartnerHandleChooseMove(u32 battler) gBattlerTarget = gBattleStruct->aiChosenTarget[battler]; u32 moveTarget = GetBattlerMoveTargetType(battler, moveInfo->moves[chosenMoveId]); - if (moveTarget & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED)) + if (moveTarget & MOVE_TARGET_USER) gBattlerTarget = battler; else if (moveTarget & MOVE_TARGET_BOTH) { diff --git a/src/battle_gfx_sfx_util.c b/src/battle_gfx_sfx_util.c index a71ee9c94f6c..d70390e926ae 100644 --- a/src/battle_gfx_sfx_util.c +++ b/src/battle_gfx_sfx_util.c @@ -326,7 +326,7 @@ static u8 GetBattlePalaceMoveGroup(u8 battler, u16 move) switch (GetBattlerMoveTargetType(battler, move)) { case MOVE_TARGET_SELECTED: - case MOVE_TARGET_USER_OR_SELECTED: + case MOVE_TARGET_OPPONENT: case MOVE_TARGET_RANDOM: case MOVE_TARGET_BOTH: case MOVE_TARGET_FOES_AND_ALLY: diff --git a/src/battle_gimmick.c b/src/battle_gimmick.c index 540b1af1f154..728ff361a910 100644 --- a/src/battle_gimmick.c +++ b/src/battle_gimmick.c @@ -371,10 +371,10 @@ void UpdateIndicatorLevelData(u32 healthboxId, u32 level) static const s8 sIndicatorPositions[][2] = { - [B_POSITION_PLAYER_LEFT] = {53, -9}, - [B_POSITION_OPPONENT_LEFT] = {44, -9}, - [B_POSITION_PLAYER_RIGHT] = {52, -9}, - [B_POSITION_OPPONENT_RIGHT] = {44, -9}, + [B_POSITION_PLAYER_LEFT] = {49, -9}, + [B_POSITION_OPPONENT_LEFT] = {40, -9}, + [B_POSITION_PLAYER_RIGHT] = {48, -9}, + [B_POSITION_OPPONENT_RIGHT] = {40, -9}, }; void CreateIndicatorSprite(u32 battler) diff --git a/src/battle_main.c b/src/battle_main.c index d7926834d987..b24b2dcb0282 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3205,6 +3205,9 @@ void SwitchInClearSetData(u32 battler) gCurrentMove = MOVE_NONE; gBattleStruct->arenaTurnCounter = 0xFF; + // Restore struct member so replacement does not miss timing + gSpecialStatuses[battler].switchInAbilityDone = FALSE; + // Reset damage to prevent things like red card activating if the switched-in mon is holding it gSpecialStatuses[battler].physicalDmg = 0; gSpecialStatuses[battler].specialDmg = 0; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 082252a29d58..be57f4ae1828 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1197,6 +1197,9 @@ static void Cmd_attackcanceler(void) if (AbilityBattleEffects(ABILITYEFFECT_MOVES_BLOCK, gBattlerTarget, 0, 0, 0)) return; + if (gMovesInfo[gCurrentMove].effect == EFFECT_PARALYZE && AbilityBattleEffects(ABILITYEFFECT_ABSORBING, gBattlerTarget, 0, 0, gCurrentMove)) + return; + if (!gBattleMons[gBattlerAttacker].pp[gCurrMovePos] && gCurrentMove != MOVE_STRUGGLE && !(gHitMarker & (HITMARKER_ALLOW_NO_PP | HITMARKER_NO_ATTACKSTRING | HITMARKER_NO_PPDEDUCT)) && !(gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS)) @@ -3884,7 +3887,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) gProtectStructs[gBattlerTarget].banefulBunkered = FALSE; gProtectStructs[gBattlerTarget].obstructed = FALSE; gProtectStructs[gBattlerTarget].silkTrapped = FALSE; - gProtectStructs[gBattlerAttacker].burningBulwarked = FALSE; + gProtectStructs[gBattlerTarget].burningBulwarked = FALSE; BattleScriptPush(gBattlescriptCurrInstr + 1); if (gCurrentMove == MOVE_HYPERSPACE_FURY) gBattlescriptCurrInstr = BattleScript_HyperspaceFuryRemoveProtect; @@ -6360,6 +6363,7 @@ static void Cmd_moveend(void) else if (moveRecoil > 0 && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) && IsBattlerAlive(gBattlerAttacker) + && IsBattlerTurnDamaged(gBattlerTarget) && gBattleScripting.savedDmg != 0) // Some checks may be redundant alongside this one { gBattleStruct->moveDamage[gBattlerAttacker] = max(1, gBattleScripting.savedDmg * max(1, moveRecoil) / 100); @@ -11605,7 +11609,8 @@ static void TryResetProtectUseCounter(u32 battler) u32 lastEffect = GetMoveEffect(lastMove); if (lastMove == MOVE_UNAVAILABLE || (!gBattleMoveEffects[lastEffect].usesProtectCounter - && (B_ALLY_SWITCH_FAIL_CHANCE >= GEN_9 && lastEffect != EFFECT_ALLY_SWITCH))) + && ((B_ALLY_SWITCH_FAIL_CHANCE >= GEN_9 && lastEffect != EFFECT_ALLY_SWITCH) + || B_ALLY_SWITCH_FAIL_CHANCE < GEN_9))) gDisableStructs[battler].protectUses = 0; } diff --git a/src/battle_util.c b/src/battle_util.c index 078d2a9a1c38..04f641ddac77 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3887,6 +3887,21 @@ bool32 HasNoMonsToSwitch(u32 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2 playerId = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); party = gEnemyParty; + // Edge case: If both opposing Pokemon were knocked out on the same turn, + // make sure opponent only sents out the final Pokemon once. + if (battler == playerId + && (gHitMarker & HITMARKER_FAINTED(flankId)) + && (gHitMarker & HITMARKER_FAINTED(playerId))) + { + u8 count = 0; + for (i = 0; i < PARTY_SIZE; i++) + if (IsValidForBattle(&party[i])) + count++; + + if (count < 2) + return TRUE; + } + if (partyIdBattlerOn1 == PARTY_SIZE) partyIdBattlerOn1 = gBattlerPartyIndexes[flankId]; if (partyIdBattlerOn2 == PARTY_SIZE) @@ -6340,6 +6355,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITYEFFECT_IMMUNITY: + gBattleStruct->bypassMoldBreakerChecks = TRUE; for (battler = 0; battler < gBattlersCount; battler++) { switch (GetBattlerAbility(battler)) @@ -6431,6 +6447,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 return effect; } } + gBattleStruct->bypassMoldBreakerChecks = FALSE; break; case ABILITYEFFECT_SYNCHRONIZE: if (gLastUsedAbility == ABILITY_SYNCHRONIZE && (gHitMarker & HITMARKER_SYNCHRONISE_EFFECT)) @@ -6670,7 +6687,9 @@ u32 GetBattlerAbility(u32 battler) && gBattleMons[battler].ability == ABILITY_COMATOSE) return ABILITY_NONE; - if (noAbilityShield && CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability)) + if (!gBattleStruct->bypassMoldBreakerChecks + && noAbilityShield + && CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability)) return ABILITY_NONE; return gBattleMons[battler].ability; @@ -6684,7 +6703,9 @@ u32 GetBattlerAbility(u32 battler) && noAbilityShield) return ABILITY_NONE; - if (noAbilityShield && CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability)) + if (!gBattleStruct->bypassMoldBreakerChecks + && noAbilityShield + && CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability)) return ABILITY_NONE; return gBattleMons[battler].ability; @@ -8517,6 +8538,7 @@ u32 GetBattleMoveTarget(u16 move, u8 setTarget) switch (moveTarget) { case MOVE_TARGET_SELECTED: + case MOVE_TARGET_OPPONENT: side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)); if (IsAffectedByFollowMe(gBattlerAttacker, side, move)) { @@ -8576,7 +8598,6 @@ u32 GetBattleMoveTarget(u16 move, u8 setTarget) else targetBattler = GetOpposingSideBattler(gBattlerAttacker); break; - case MOVE_TARGET_USER_OR_SELECTED: case MOVE_TARGET_USER: default: targetBattler = gBattlerAttacker; @@ -8715,7 +8736,7 @@ u32 GetBattlerHoldEffectInternal(u32 battler, bool32 checkNegating, bool32 check return HOLD_EFFECT_NONE; if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) return HOLD_EFFECT_NONE; - if (checkAbility && GetBattlerAbility(battler) == ABILITY_KLUTZ) + if (checkAbility && GetBattlerAbility(battler) == ABILITY_KLUTZ && !(gStatuses3[battler] & STATUS3_GASTRO_ACID)) return HOLD_EFFECT_NONE; } @@ -8927,7 +8948,7 @@ u32 GetMoveTargetCount(struct DamageCalculationData *damageCalcData) case MOVE_TARGET_DEPENDS: case MOVE_TARGET_SELECTED: case MOVE_TARGET_RANDOM: - case MOVE_TARGET_USER_OR_SELECTED: + case MOVE_TARGET_OPPONENT: return IsBattlerAlive(battlerDef); case MOVE_TARGET_USER: return IsBattlerAlive(battlerAtk); @@ -11533,8 +11554,12 @@ bool32 CanStealItem(u32 battlerStealing, u32 battlerItem, u16 item) return FALSE; } + // It's supposed to pop before trying to steal but this also works + if (ItemId_GetHoldEffect(item) == HOLD_EFFECT_AIR_BALLOON) + return FALSE; + if (!CanBattlerGetOrLoseItem(battlerItem, item) // Battler with item cannot have it stolen - ||!CanBattlerGetOrLoseItem(battlerStealing, item)) // Stealer cannot take the item + || !CanBattlerGetOrLoseItem(battlerStealing, item)) // Stealer cannot take the item return FALSE; return TRUE; diff --git a/src/contest.c b/src/contest.c index b99dd6e93546..be17d8394110 100644 --- a/src/contest.c +++ b/src/contest.c @@ -5485,10 +5485,10 @@ static void SetMoveTargetPosition(u16 move) { switch (GetBattlerMoveTargetType(gBattlerAttacker, move)) { - case MOVE_TARGET_USER_OR_SELECTED: case MOVE_TARGET_USER: gBattlerTarget = B_POSITION_PLAYER_RIGHT; break; + case MOVE_TARGET_OPPONENT: case MOVE_TARGET_SELECTED: case MOVE_TARGET_RANDOM: case MOVE_TARGET_BOTH: diff --git a/src/data/graphics/gimmicks.h b/src/data/graphics/gimmicks.h index 5299f74fc3db..f16b2f83c009 100644 --- a/src/data/graphics/gimmicks.h +++ b/src/data/graphics/gimmicks.h @@ -135,8 +135,8 @@ static const struct SpritePalette sSpritePalette_TeraIndicator = {sTeraIndicator static const struct OamData sOamData_GimmickIndicator = { - .shape = SPRITE_SHAPE(16x16), - .size = SPRITE_SIZE(16x16), + .shape = SPRITE_SHAPE(8x16), + .size = SPRITE_SIZE(8x16), .priority = 1, }; diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 0adc112f3118..f9897650a541 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -9892,7 +9892,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .type = TYPE_NORMAL, .accuracy = 0, .pp = 20, - .target = MOVE_TARGET_SELECTED, + .target = MOVE_TARGET_OPPONENT, .priority = 0, .category = DAMAGE_CATEGORY_STATUS, .zMove = { .effect = Z_EFFECT_SPD_UP_2 }, diff --git a/src/item_use.c b/src/item_use.c index 64db57a16d06..c609614292aa 100644 --- a/src/item_use.c +++ b/src/item_use.c @@ -1186,6 +1186,15 @@ void ItemUseInBattle_PartyMenuChooseMove(u8 taskId) ItemUseInBattle_ShowPartyMenu(taskId); } +static bool32 SelectedMonHasStatus2(u16 itemId) +{ + if (gPartyMenu.slotId == 0) + return gBattleMons[0].status2 & GetItemStatus2Mask(itemId); + else if (gBattleTypeFlags & (BATTLE_TYPE_DOUBLE | BATTLE_TYPE_MULTI) && gPartyMenu.slotId == 1) + return gBattleMons[2].status2 & GetItemStatus2Mask(itemId); + return FALSE; +} + // Returns whether an item can be used in battle and sets the fail text. bool32 CannotUseItemsInBattle(u16 itemId, struct Pokemon *mon) { @@ -1258,13 +1267,13 @@ bool32 CannotUseItemsInBattle(u16 itemId, struct Pokemon *mon) break; case EFFECT_ITEM_CURE_STATUS: if (!((GetMonData(mon, MON_DATA_STATUS) & GetItemStatus1Mask(itemId)) - || (gPartyMenu.slotId == 0 && gBattleMons[gBattlerInMenuId].status2 & GetItemStatus2Mask(itemId)))) + || SelectedMonHasStatus2(itemId))) cannotUse = TRUE; break; case EFFECT_ITEM_HEAL_AND_CURE_STATUS: if ((hp == 0 || hp == GetMonData(mon, MON_DATA_MAX_HP)) && !((GetMonData(mon, MON_DATA_STATUS) & GetItemStatus1Mask(itemId)) - || (gPartyMenu.slotId == 0 && gBattleMons[gBattlerInMenuId].status2 & GetItemStatus2Mask(itemId)))) + || SelectedMonHasStatus2(itemId))) cannotUse = TRUE; break; case EFFECT_ITEM_REVIVE: diff --git a/test/battle/ability/intimidate.c b/test/battle/ability/intimidate.c index 727a4086a793..61b53f42e114 100644 --- a/test/battle/ability/intimidate.c +++ b/test/battle/ability/intimidate.c @@ -375,3 +375,18 @@ DOUBLE_BATTLE_TEST("Intimidate will correctly decrease the attack of the second ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); } } + +SINGLE_BATTLE_TEST("Intimdate does not lose timing after mega evolution and switch out by a hit escape move") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_U_TURN].effect == EFFECT_HIT_ESCAPE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MANECTRIC) { Item(ITEM_MANECTITE); } + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_U_TURN, gimmick: GIMMICK_MEGA); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + } +} diff --git a/test/battle/ability/motor_drive.c b/test/battle/ability/motor_drive.c new file mode 100644 index 000000000000..83dc2a37e1c0 --- /dev/null +++ b/test/battle/ability/motor_drive.c @@ -0,0 +1,14 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Motor Drive absorbs status moves") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EMOLGA) { Ability(ABILITY_MOTOR_DRIVE); } + } WHEN { + TURN { MOVE(player, MOVE_THUNDER_WAVE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_MOTOR_DRIVE); + } +} diff --git a/test/battle/ability/own_tempo.c b/test/battle/ability/own_tempo.c index e9abcf1cf452..a061f820ba83 100644 --- a/test/battle/ability/own_tempo.c +++ b/test/battle/ability/own_tempo.c @@ -58,7 +58,6 @@ SINGLE_BATTLE_TEST("Own Tempo prevents confusion from moves by the user") SINGLE_BATTLE_TEST("Mold Breaker ignores Own Tempo") { - KNOWN_FAILING; // Ideally the func CanBeConfused should be split into AttackerCanBeConfused and TargetCanBeConfused or we do it in the same func but have a check for when battlerAtk == battlerDef GIVEN { ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE); PLAYER(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); } @@ -66,16 +65,13 @@ SINGLE_BATTLE_TEST("Mold Breaker ignores Own Tempo") } WHEN { TURN { MOVE(player, MOVE_CONFUSE_RAY); } } SCENE { - NONE_OF { - ABILITY_POPUP(opponent, ABILITY_OWN_TEMPO); - MESSAGE("The opposing Slowpoke's Own Tempo prevents confusion!"); - } + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); + NOT MESSAGE("The opposing Slowpoke's Own Tempo prevents confusion!"); } } SINGLE_BATTLE_TEST("Mold Breaker does not prevent Own Tempo from curing confusion right after") { - KNOWN_FAILING; GIVEN { ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE); PLAYER(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); }; diff --git a/test/battle/ability/pastel_veil.c b/test/battle/ability/pastel_veil.c index 686ca0ff85df..5f6fa6b058b0 100644 --- a/test/battle/ability/pastel_veil.c +++ b/test/battle/ability/pastel_veil.c @@ -33,7 +33,6 @@ DOUBLE_BATTLE_TEST("Pastel Veil prevents Poison Sting poison on partner") SINGLE_BATTLE_TEST("Pastel Veil immediately cures Mold Breaker poison") { - KNOWN_FAILING; GIVEN { ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_TOXIC); PLAYER(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); } diff --git a/test/battle/hold_effect/air_balloon.c b/test/battle/hold_effect/air_balloon.c index 302a0e63971d..cb06743e4c49 100644 --- a/test/battle/hold_effect/air_balloon.c +++ b/test/battle/hold_effect/air_balloon.c @@ -105,7 +105,6 @@ SINGLE_BATTLE_TEST("Air Balloon pops before it can be stolen with Magician") SINGLE_BATTLE_TEST("Air Balloon pops before it can be stolen with Thief or Covet") { u32 move; - KNOWN_FAILING; PARAMETRIZE { move = MOVE_THIEF; } PARAMETRIZE { move = MOVE_COVET; } GIVEN { diff --git a/test/battle/hold_effect/jaboca_berry.c b/test/battle/hold_effect/jaboca_berry.c index 756b5adf3c75..e446c4253d33 100644 --- a/test/battle/hold_effect/jaboca_berry.c +++ b/test/battle/hold_effect/jaboca_berry.c @@ -54,7 +54,7 @@ SINGLE_BATTLE_TEST("Jaboca Berry tirggers before Bug Bite can steal it") HP_BAR(opponent); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); HP_BAR(player); - MESSAGE("Wyanut was hurt by the opposing Wobbuffet's Jaboca Berry!"); - NOT MESSAGE("Wynaut stole and ate the opposing its target's Jaboca Berry!"); + MESSAGE("Wynaut was hurt by the opposing Wobbuffet's Jaboca Berry!"); + NOT MESSAGE("Wynaut stole and ate the opposing Wobbuffet's Jaboca Berry!"); } } diff --git a/test/battle/move_effect/coaching.c b/test/battle/move_effect/coaching.c index 0cfc593d3529..812063579d62 100644 --- a/test/battle/move_effect/coaching.c +++ b/test/battle/move_effect/coaching.c @@ -58,7 +58,6 @@ DOUBLE_BATTLE_TEST("Coaching bypasses Crafty Shield") DOUBLE_BATTLE_TEST("Coaching fails if all allies are is semi-invulnerable") { - KNOWN_FAILING; // Coaching succeeds GIVEN { ASSUME(GetMoveEffect(MOVE_FLY) == EFFECT_SEMI_INVULNERABLE); PLAYER(SPECIES_WOBBUFFET); diff --git a/test/battle/move_effect/two_turns_attack.c b/test/battle/move_effect/two_turns_attack.c index 200fc72d1874..4dd79ade46c2 100644 --- a/test/battle/move_effect/two_turns_attack.c +++ b/test/battle/move_effect/two_turns_attack.c @@ -43,7 +43,6 @@ SINGLE_BATTLE_TEST("Razor Wind needs a charging turn") SINGLE_BATTLE_TEST("Razor Wind doesn't need to charge with Power Herb") { - KNOWN_FAILING; GIVEN { PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_POWER_HERB); } OPPONENT(SPECIES_WOBBUFFET); @@ -63,7 +62,6 @@ SINGLE_BATTLE_TEST("Razor Wind doesn't need to charge with Power Herb") MESSAGE("Wobbuffet became fully charged due to its Power Herb!"); if (B_UPDATED_MOVE_DATA < GEN_5) MESSAGE("Wobbuffet used Razor Wind!"); - // For some reason, this breaks with and only with Razor Wind... ANIMATION(ANIM_TYPE_MOVE, MOVE_RAZOR_WIND, player); HP_BAR(opponent); } diff --git a/test/battle/move_flags/recoil.c b/test/battle/move_flags/recoil.c index dede6289da06..5a8f3be57665 100644 --- a/test/battle/move_flags/recoil.c +++ b/test/battle/move_flags/recoil.c @@ -83,3 +83,22 @@ SINGLE_BATTLE_TEST("Flare Blitz deals 33% of recoil damage to the user and can b EXPECT_MUL_EQ(directDamage, UQ_4_12(0.33), recoilDamage); } } + +SINGLE_BATTLE_TEST("Flare Blitz is absorbed by Flash Fire and no recoil damage is dealt") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_FLARE_BLITZ].recoil > 0); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VULPIX) { Ability(ABILITY_FLASH_FIRE); }; + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_FLARE_BLITZ); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + HP_BAR(player); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLARE_BLITZ, player); + HP_BAR(opponent); + HP_BAR(player); + } + } +} diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index 2b6f16246bf3..40e8b31f4182 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -2043,7 +2043,7 @@ s32 MoveGetTarget(s32 battlerId, u32 moveId, struct MoveContext *ctx, u32 source { target = BATTLE_OPPOSITE(battlerId); } - else if (move->target == MOVE_TARGET_SELECTED) + else if (move->target == MOVE_TARGET_SELECTED || move->target == MOVE_TARGET_OPPONENT) { // In AI Doubles not specified target allows any target for EXPECT_MOVE. if (GetBattleTest()->type != BATTLE_TEST_AI_DOUBLES)