diff --git a/include/battle_main.h b/include/battle_main.h index e8368f82d6..18f2210588 100644 --- a/include/battle_main.h +++ b/include/battle_main.h @@ -71,8 +71,8 @@ void SwitchPartyOrder(u32 battlerId); void SwapTurnOrder(u8 id1, u8 id2); u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, u32 holdEffect); u32 GetBattlerTotalSpeedStat(u32 battler); -s8 GetChosenMovePriority(u32 battlerId); -s8 GetBattleMovePriority(u32 battlerId, u16 move); +s32 GetChosenMovePriority(u32 battler, u32 ability); +s32 GetBattleMovePriority(u32 battler, u32 ability, u32 move); s32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMoves, u32 ability1, u32 ability2, u32 holdEffectBattler1, u32 holdEffectBattler2, u32 speedBattler1, u32 speedBattler2, s32 priority1, s32 priority2); s32 GetWhichBattlerFasterOrTies(u32 battler1, u32 battler2, bool32 ignoreChosenMoves); diff --git a/include/battle_util.h b/include/battle_util.h index c7f2bd44b8..b8e2c91ccc 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -25,6 +25,7 @@ enum AbilityEffectOptions { ABILITY_CHECK_TRIGGER, + ABILITY_CHECK_TRIGGER_AI, ABILITY_RUN_SCRIPT, }; @@ -39,10 +40,6 @@ enum MoveAbsorbed enum { ABILITYEFFECT_ON_SWITCHIN, ABILITYEFFECT_ENDTURN, - ABILITYEFFECT_MOVES_BLOCK, - ABILITYEFFECT_WOULD_BLOCK, // Checks immunity without triggering a script - ABILITYEFFECT_ABSORBING, - ABILITYEFFECT_WOULD_ABSORB, // Checks immunity without triggering a script ABILITYEFFECT_MOVE_END_ATTACKER, ABILITYEFFECT_MOVE_END, ABILITYEFFECT_IMMUNITY, @@ -218,7 +215,7 @@ u32 AtkCanceller_MoveSuccessOrder(void); void SetAtkCancellerForCalledMove(void); bool32 HasNoMonsToSwitch(u32 battler, u8 r1, u8 r2); bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, bool32 viaAbility); -bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 move, u32 abilityDef, enum AbilityEffectOptions option); +bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 abilityAtk, u32 abilityDef, u32 move, enum AbilityEffectOptions option); bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 move, u32 moveType, enum AbilityEffectOptions option); u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 moveArg); bool32 TryPrimalReversion(u32 battler); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 6c651d6639..a9f9a8e5e2 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -825,7 +825,6 @@ void BattleAI_DoAIProcessing_PredictedSwitchin(struct AI_ThinkingStruct *aiThink static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { // move data - s8 atkPriority = GetBattleMovePriority(battlerAtk, move); u32 moveEffect = GetMoveEffect(move); s32 moveType; u32 moveTarget = GetBattlerMoveTargetType(battlerAtk, move); @@ -835,7 +834,9 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) u32 i; u32 weather; u32 predictedMove = aiData->lastUsedMove[battlerDef]; + u32 abilityAtk = aiData->abilities[battlerAtk]; u32 abilityDef = aiData->abilities[battlerDef]; + s32 atkPriority = GetBattleMovePriority(battlerAtk, abilityAtk, move); if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) return score; @@ -896,16 +897,16 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } } - if (DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move)) + if (DoesBattlerIgnoreAbilityChecks(battlerAtk, abilityAtk, move)) abilityDef = ABILITY_NONE; // check non-user target if (!(moveTarget & MOVE_TARGET_USER)) { - if (CanAbilityBlockMove(battlerAtk, battlerDef, move, abilityDef, ABILITY_CHECK_TRIGGER)) + if (CanAbilityBlockMove(battlerAtk, battlerDef, abilityAtk, abilityDef, move, ABILITY_CHECK_TRIGGER_AI)) RETURN_SCORE_MINUS(20); - if (CanAbilityAbsorbMove(battlerAtk, battlerDef, abilityDef, move, moveType, ABILITY_CHECK_TRIGGER)) + if (CanAbilityAbsorbMove(battlerAtk, battlerDef, abilityDef, move, moveType, ABILITY_CHECK_TRIGGER_AI)) RETURN_SCORE_MINUS(20); switch (abilityDef) @@ -1073,7 +1074,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) switch (moveEffect) { case EFFECT_HIT: // only applies to Vital Throw - if (GetBattleMovePriority(battlerAtk, move) < 0 && AI_IsFaster(battlerAtk, battlerDef, move) && aiData->hpPercents[battlerAtk] < 40) + if (GetBattleMovePriority(battlerAtk, aiData->abilities[battlerAtk], move) < 0 && AI_IsFaster(battlerAtk, battlerDef, move) && aiData->hpPercents[battlerAtk] < 40) ADJUST_SCORE(-2); // don't want to move last break; default: @@ -2673,8 +2674,15 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_UPPER_HAND: - if (predictedMove == MOVE_NONE || IsBattleMoveStatus(predictedMove) || AI_IsSlower(battlerAtk, battlerDef, move) || GetBattleMovePriority(battlerDef, predictedMove) < 1 || GetBattleMovePriority(battlerDef, predictedMove) > 3) // Opponent going first or not using priority move - ADJUST_SCORE(-10); + { + u32 defPrio = GetBattleMovePriority(battlerDef, aiData->abilities[battlerDef], predictedMove); + if (predictedMove == MOVE_NONE + || IsBattleMoveStatus(predictedMove) + || AI_IsSlower(battlerAtk, battlerDef, move) + || defPrio < 1 + || defPrio > 3) // Opponent going first or not using priority move + ADJUST_SCORE(-10); + } break; case EFFECT_PLACEHOLDER: return 0; // cannot even select @@ -2723,7 +2731,7 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } else if (CanTargetFaintAi(battlerDef, battlerAtk) && GetWhichBattlerFasterOrTies(battlerAtk, battlerDef, TRUE) != AI_IS_FASTER - && GetBattleMovePriority(battlerAtk, move) > 0) + && GetBattleMovePriority(battlerAtk, AI_DATA->abilities[battlerAtk], move) > 0) { ADJUST_SCORE(LAST_CHANCE); } @@ -4856,7 +4864,7 @@ static s32 AI_ForceSetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32 if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_SMART_SWITCHING && AI_IsSlower(battlerAtk, battlerDef, move) && CanTargetFaintAi(battlerDef, battlerAtk) - && GetBattleMovePriority(battlerAtk, move) == 0) + && GetBattleMovePriority(battlerAtk, AI_DATA->abilities[battlerAtk], move) == 0) { RETURN_SCORE_MINUS(20); // No point in setting up if you will faint. Should just switch if possible.. } diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index da91175f55..242c030838 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -458,7 +458,7 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) if (!(AI_THINKING_STRUCT->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)) return FALSE; if (gBattleStruct->prevTurnSpecies[battler] != gBattleMons[battler].species) // AI mon has changed, player's behaviour no longer reliable; note to override this if using AI_FLAG_PREDICT_MOVE - return FALSE; + return FALSE; if (HasSuperEffectiveMoveAgainstOpponents(battler, TRUE) && (RandomPercentage(RNG_AI_SWITCH_ABSORBING_STAY_IN, STAY_IN_ABSORBING_PERCENTAGE) || AI_DATA->aiSwitchPredictionInProgress)) return FALSE; if (AreStatsRaised(battler)) @@ -681,7 +681,7 @@ static bool32 ShouldSwitchIfBadlyStatused(u32 battler) && !(gBattleMons[battler].status2 & STATUS2_FORESIGHT) && !(gStatuses3[battler] & STATUS3_MIRACLE_EYED)) switchMon = FALSE; - + if (switchMon) return SetSwitchinAndSwitch(battler, PARTY_SIZE); } @@ -999,7 +999,7 @@ static bool32 ShouldSwitchIfBadChoiceLock(u32 battler) if (lastUsedMove != MOVE_NONE && (AI_GetMoveEffectiveness(lastUsedMove, battler, opposingBattler) == UQ_4_12(0.0) || CanAbilityAbsorbMove(battler, opposingBattler, AI_DATA->abilities[opposingBattler], lastUsedMove, GetMoveType(lastUsedMove), ABILITY_CHECK_TRIGGER) - || CanAbilityBlockMove(battler, opposingBattler, lastUsedMove, AI_DATA->abilities[opposingBattler], ABILITY_CHECK_TRIGGER))) + || CanAbilityBlockMove(battler, opposingBattler, AI_DATA->abilities[battler], AI_DATA->abilities[opposingBattler], lastUsedMove, ABILITY_CHECK_TRIGGER))) moveAffectsTarget = FALSE; if (HOLD_EFFECT_CHOICE(holdEffect) && IsBattlerItemEnabled(battler)) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index fee630cedb..a43afbacda 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -478,10 +478,10 @@ bool32 IsDamageMoveUnusable(u32 battlerAtk, u32 battlerDef, u32 move, u32 moveTy if (gBattleStruct->battlerState[battlerDef].commandingDondozo) return TRUE; - if (CanAbilityBlockMove(battlerAtk, battlerDef, move, aiData->abilities[battlerDef], ABILITY_CHECK_TRIGGER)) + if (CanAbilityBlockMove(battlerAtk, battlerDef, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], move, ABILITY_CHECK_TRIGGER_AI)) return TRUE; - if (CanAbilityAbsorbMove(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, moveType, ABILITY_CHECK_TRIGGER)) + if (CanAbilityAbsorbMove(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, moveType, ABILITY_CHECK_TRIGGER_AI)) return TRUE; switch (GetMoveEffect(move)) @@ -1142,8 +1142,8 @@ s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 moveConsidered) u32 predictedMove = AI_DATA->lastUsedMove[battler]; // TODO update for move prediction - s8 aiPriority = GetBattleMovePriority(battlerAI, moveConsidered); - s8 playerPriority = GetBattleMovePriority(battler, predictedMove); + s8 aiPriority = GetBattleMovePriority(battlerAI, abilityAI, moveConsidered); + s8 playerPriority = GetBattleMovePriority(battler, abilityPlayer, predictedMove); if (aiPriority > playerPriority) return AI_IS_FASTER; diff --git a/src/battle_main.c b/src/battle_main.c index 6845190d10..215ee71268 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4796,23 +4796,22 @@ u32 GetBattlerTotalSpeedStat(u32 battler) return GetBattlerTotalSpeedStatArgs(battler, ability, holdEffect); } -s8 GetChosenMovePriority(u32 battler) +s32 GetChosenMovePriority(u32 battler, u32 ability) { u16 move; - gProtectStructs[battler].pranksterElevated = 0; + gProtectStructs[battler].pranksterElevated = FALSE; if (gProtectStructs[battler].noValidMoves) move = MOVE_STRUGGLE; else move = gBattleMons[battler].moves[gBattleStruct->chosenMovePositions[battler]]; - return GetBattleMovePriority(battler, move); + return GetBattleMovePriority(battler, ability, move); } -s8 GetBattleMovePriority(u32 battler, u16 move) +s32 GetBattleMovePriority(u32 battler, u32 ability, u32 move) { - s8 priority; - u16 ability = GetBattlerAbility(battler); + s32 priority = 0; if (GetActiveGimmick(battler) == GIMMICK_Z_MOVE && !IsBattleMoveStatus(move)) move = GetUsableZMove(battler, move); @@ -4924,9 +4923,9 @@ s32 GetWhichBattlerFasterOrTies(u32 battler1, u32 battler2, bool32 ignoreChosenM if (!ignoreChosenMoves) { if (gChosenActionByBattler[battler1] == B_ACTION_USE_MOVE) - priority1 = GetChosenMovePriority(battler1); + priority1 = GetChosenMovePriority(battler1, ability1); if (gChosenActionByBattler[battler2] == B_ACTION_USE_MOVE) - priority2 = GetChosenMovePriority(battler2); + priority2 = GetChosenMovePriority(battler2, ability2); } return GetWhichBattlerFasterArgs( diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 0269334c18..2b662cdbf0 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1236,8 +1236,6 @@ static void Cmd_attackcanceler(void) { CMD_ARGS(); - s32 i; - if (gBattleStruct->battlerState[gBattlerAttacker].usedEjectItem) { gBattleStruct->battlerState[gBattlerAttacker].usedEjectItem = FALSE; @@ -1274,10 +1272,27 @@ static void Cmd_attackcanceler(void) return; } - if (AbilityBattleEffects(ABILITYEFFECT_MOVES_BLOCK, gBattlerTarget, 0, 0, 0)) - return; - if (gMovesInfo[gCurrentMove].effect == EFFECT_PARALYZE && AbilityBattleEffects(ABILITYEFFECT_ABSORBING, gBattlerTarget, 0, 0, gCurrentMove)) + + u32 abilityDef = GetBattlerAbility(gBattlerTarget); + if (CanAbilityBlockMove(gBattlerAttacker, + gBattlerTarget, + GetBattlerAbility(gBattlerAttacker), + abilityDef, + gCurrentMove, + ABILITY_RUN_SCRIPT)) return; + + if (effect == EFFECT_PARALYZE) + { + if (CanAbilityAbsorbMove(gBattlerAttacker, + gBattlerTarget, + abilityDef, + gCurrentMove, + GetBattleMoveType(gCurrentMove), + ABILITY_RUN_SCRIPT)) + return; + } + if (IsMovePowderBlocked(gBattlerAttacker, gBattlerTarget, gCurrentMove)) return; @@ -1337,7 +1352,7 @@ static void Cmd_attackcanceler(void) { u32 battler = gBattlerTarget; - if (GetBattlerAbility(gBattlerTarget) == ABILITY_MAGIC_BOUNCE) + if (abilityDef == ABILITY_MAGIC_BOUNCE) { battler = gBattlerTarget; gBattleStruct->bouncedMoveIsUsed = TRUE; @@ -1371,7 +1386,7 @@ static void Cmd_attackcanceler(void) return; } - for (i = 0; i < gBattlersCount; i++) + for (u32 i = 0; i < gBattlersCount; i++) { if ((gProtectStructs[gBattlerByTurnOrder[i]].stealMove) && MoveCanBeSnatched(gCurrentMove)) { @@ -1434,39 +1449,33 @@ static void Cmd_attackcanceler(void) } } -static bool32 JumpIfMoveFailed(u8 adder, u16 move) +static void JumpIfMoveFailed(u32 adder, u32 move, u32 moveType) { if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) { gLastLandedMoves[gBattlerTarget] = 0; gLastHitByType[gBattlerTarget] = 0; gBattlescriptCurrInstr = T1_READ_PTR(gBattlescriptCurrInstr + 1); - return TRUE; + return; } else { TrySetDestinyBondToHappen(); - if (AbilityBattleEffects(ABILITYEFFECT_ABSORBING, gBattlerTarget, 0, 0, move)) - return TRUE; + + if (CanAbilityAbsorbMove(gBattlerAttacker, + gBattlerTarget, + GetBattlerAbility(gBattlerTarget), + move, + moveType, + ABILITY_RUN_SCRIPT)) + return; } + gBattlescriptCurrInstr += adder; - return FALSE; } static void Cmd_unused5(void) { - CMD_ARGS(const u8 *failInstr); - - if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove)) - { - gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; - JumpIfMoveFailed(sizeof(*cmd), MOVE_NONE); - gBattleCommunication[MISS_TYPE] = B_MSG_PROTECTED; - } - else - { - gBattlescriptCurrInstr = cmd->nextInstr; - } } static bool32 JumpIfMoveAffectedByProtect(u32 move, u32 battler, u32 shouldJump) @@ -1476,7 +1485,7 @@ static bool32 JumpIfMoveAffectedByProtect(u32 move, u32 battler, u32 shouldJump) { gBattleStruct->moveResultFlags[battler] |= MOVE_RESULT_MISSED; if (shouldJump) - JumpIfMoveFailed(7, move); + JumpIfMoveFailed(7, move, GetBattleMoveType(move)); } return affected; } @@ -1686,16 +1695,12 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u8 *failInstr, u16 move) { - u32 abilityAtk; - u32 holdEffectAtk; - if (move == ACC_CURR_MOVE) move = gCurrentMove; u32 effect = GetMoveEffect(move); - - abilityAtk = GetBattlerAbility(gBattlerAttacker); - holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker, TRUE); + u32 abilityAtk = GetBattlerAbility(gBattlerAttacker); + u32 holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker, TRUE); if (move == NO_ACC_CALC_CHECK_LOCK_ON) { @@ -1710,7 +1715,12 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u if (gProtectStructs[gBattlerTarget].protected == PROTECT_MAX_GUARD) gBattlescriptCurrInstr = nextInstr; else - AbilityBattleEffects(ABILITYEFFECT_ABSORBING, gBattlerTarget, 0, 0, gCurrentMove); + CanAbilityAbsorbMove(gBattlerAttacker, + gBattlerTarget, + GetBattlerAbility(gBattlerTarget), + gCurrentMove, + GetBattleMoveType(gCurrentMove), + ABILITY_RUN_SCRIPT); } } else if (gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_2ND_HIT @@ -1778,7 +1788,7 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u if (calcSpreadMove) gBattleStruct->calculatedSpreadMoveAccuracy = TRUE; - JumpIfMoveFailed(7, move); + JumpIfMoveFailed(7, move, moveType); } } @@ -17845,10 +17855,14 @@ void BS_TryUpperHand(void) { NATIVE_ARGS(const u8 *failInstr); + u32 abilityDef = GetBattlerAbility(gBattlerTarget); + u32 prio = GetChosenMovePriority(gBattlerTarget, abilityDef); + if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget) || gChosenMoveByBattler[gBattlerTarget] == MOVE_NONE || IsBattleMoveStatus(gChosenMoveByBattler[gBattlerTarget]) - || GetChosenMovePriority(gBattlerTarget) < 1 || GetChosenMovePriority(gBattlerTarget) > 3) // Fails if priority is less than 1 or greater than 3, if target already moved, or if using a status + || prio < 1 + || prio > 3) // Fails if priority is less than 1 or greater than 3, if target already moved, or if using a status gBattlescriptCurrInstr = cmd->failInstr; else gBattlescriptCurrInstr = cmd->nextInstr; diff --git a/src/battle_util.c b/src/battle_util.c index 69cd97d315..ad173e1303 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3626,7 +3626,7 @@ static void CancellerPsychicTerrain(u32 *effect) { if (gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN && IsBattlerGrounded(gBattlerTarget) - && GetChosenMovePriority(gBattlerAttacker) > 0 + && GetChosenMovePriority(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) > 0 && GetMoveTarget(gCurrentMove) != MOVE_TARGET_ALL_BATTLERS && GetMoveTarget(gCurrentMove) != MOVE_TARGET_OPPONENTS_FIELD && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget)) @@ -3758,6 +3758,8 @@ static void CancellerZMoves(u32 *effect) static void CancellerMultiTargetMoves(u32 *effect) { u32 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); + u32 abilityAtk = GetBattlerAbility(gBattlerAttacker); + if (IsSpreadMove(moveTarget)) { for (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++) @@ -3765,6 +3767,8 @@ static void CancellerMultiTargetMoves(u32 *effect) if (gBattleStruct->bouncedMoveIsUsed && B_SIDE_OPPONENT == GetBattlerSide(battlerDef)) continue; + u32 abilityDef = GetBattlerAbility(battlerDef); + if (gBattlerAttacker == battlerDef || !IsBattlerAlive(battlerDef) || (moveTarget == MOVE_TARGET_BOTH && gBattlerAttacker == BATTLE_PARTNER(battlerDef)) @@ -3773,13 +3777,13 @@ static void CancellerMultiTargetMoves(u32 *effect) gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_NO_EFFECT; gBattleStruct->noResultString[battlerDef] = TRUE; } - else if (AbilityBattleEffects(ABILITYEFFECT_WOULD_BLOCK, battlerDef, 0, 0, 0) - || (IsBattlerTerrainAffected(gBattlerAttacker, STATUS_FIELD_PSYCHIC_TERRAIN) && GetBattleMovePriority(gBattlerAttacker, gCurrentMove) > 0)) + else if (CanAbilityBlockMove(gBattlerAttacker, battlerDef, abilityAtk, abilityDef, gCurrentMove, ABILITY_CHECK_TRIGGER) + || (IsBattlerTerrainAffected(gBattlerAttacker, STATUS_FIELD_PSYCHIC_TERRAIN) && GetBattleMovePriority(gBattlerAttacker, abilityAtk, gCurrentMove) > 0)) { gBattleStruct->moveResultFlags[battlerDef] = 0; gBattleStruct->noResultString[battlerDef] = TRUE; } - else if (AbilityBattleEffects(ABILITYEFFECT_WOULD_ABSORB, battlerDef, 0, 0, gCurrentMove)) + else if (CanAbilityAbsorbMove(gBattlerAttacker, battlerDef, abilityDef, gCurrentMove, GetBattleMoveType(gCurrentMove), ABILITY_CHECK_TRIGGER)) { gBattleStruct->moveResultFlags[battlerDef] = 0; gBattleStruct->noResultString[battlerDef] = DO_ACCURACY_CHECK; @@ -4216,17 +4220,21 @@ static void ChooseStatBoostAnimation(u32 battler) #undef ANIM_STAT_ACC #undef ANIM_STAT_EVASION -bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 move, u32 abilityDef, enum AbilityEffectOptions option) +bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 abilityAtk, u32 abilityDef, u32 move, enum AbilityEffectOptions option) { const u8 *battleScriptBlocksMove = NULL; - s32 atkPriority = AI_DATA->aiCalcInProgress ? GetBattleMovePriority(battlerAtk, move) : GetChosenMovePriority(battlerAtk); - u32 moveTarget = GetBattlerMoveTargetType(battlerAtk, move); u32 battlerAbility = battlerDef; + s32 atkPriority = 0; + + if (option == ABILITY_CHECK_TRIGGER_AI) + atkPriority = GetBattleMovePriority(battlerAtk, abilityAtk, move); + else + atkPriority = GetChosenMovePriority(battlerAtk, abilityAtk); switch (abilityDef) { case ABILITY_SOUNDPROOF: - if (IsSoundMove(move) && !(moveTarget & MOVE_TARGET_USER)) + if (IsSoundMove(move) && !(GetBattlerMoveTargetType(battlerAtk, move) & MOVE_TARGET_USER)) { if (gBattleMons[battlerAtk].status2 & STATUS2_MULTIPLETURNS) gHitMarker |= HITMARKER_NO_PPDEDUCT; @@ -4254,29 +4262,48 @@ bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 move, u32 ability case ABILITY_GOOD_AS_GOLD: if (IsBattleMoveStatus(move)) { - if (!(moveTarget & MOVE_TARGET_OPPONENTS_FIELD) && !(moveTarget & MOVE_TARGET_ALL_BATTLERS)) + if (!(GetBattlerMoveTargetType(battlerAtk, move) & (MOVE_TARGET_OPPONENTS_FIELD | MOVE_TARGET_ALL_BATTLERS))) battleScriptBlocksMove = BattleScript_GoodAsGoldActivates; } break; } - - // Check def partner ability - if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battlerDef))) + if (atkPriority > 0) { - switch (GetBattlerAbility(BATTLE_PARTNER(battlerDef))) + // Prankster check + if (battleScriptBlocksMove == NULL + && BlocksPrankster(move, battlerAtk, battlerDef, TRUE) + && !(IsBattleMoveStatus(move) && (abilityDef == ABILITY_MAGIC_BOUNCE || gProtectStructs[battlerDef].bounceMove))) { - case ABILITY_DAZZLING: - case ABILITY_QUEENLY_MAJESTY: - case ABILITY_ARMOR_TAIL: - if (atkPriority > 0 && !IsBattlerAlly(battlerAtk, BATTLE_PARTNER(battlerDef))) + if (option == ABILITY_RUN_SCRIPT && !IsSpreadMove(GetBattlerMoveTargetType(battlerAtk, move))) + CancelMultiTurnMoves(battlerAtk); // Don't cancel moves that can hit two targets bc one target might not be protected + + battleScriptBlocksMove = BattleScript_DarkTypePreventsPrankster; + } + + // Check def partner ability + u32 partnerDef = BATTLE_PARTNER(battlerDef); + if (battleScriptBlocksMove == NULL + && IsDoubleBattle() + && IsBattlerAlive(partnerDef) + && !IsBattlerAlly(battlerAtk, partnerDef)) + { + if (option == ABILITY_CHECK_TRIGGER_AI) + abilityDef = AI_DATA->abilities[partnerDef]; + else + abilityDef = GetBattlerAbility(partnerDef); + + switch (abilityDef) { + case ABILITY_DAZZLING: + case ABILITY_QUEENLY_MAJESTY: + case ABILITY_ARMOR_TAIL: if (gBattleMons[battlerAtk].status2 & STATUS2_MULTIPLETURNS) gHitMarker |= HITMARKER_NO_PPDEDUCT; - battlerAbility = BATTLE_PARTNER(battlerDef); + battlerAbility = partnerDef; battleScriptBlocksMove = BattleScript_DazzlingProtected; + break; } - break; } } @@ -4285,9 +4312,12 @@ bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 move, u32 ability if (option == ABILITY_RUN_SCRIPT) { + gLastUsedAbility = abilityDef; + RecordAbilityBattle(battlerDef, abilityDef); gBattleScripting.battler = gBattlerAbility = battlerAbility; gBattlescriptCurrInstr = battleScriptBlocksMove; } + return TRUE; } @@ -4304,7 +4334,7 @@ bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 effect = MOVE_ABSORBED_BY_NO_ABILITY; break; case ABILITY_VOLT_ABSORB: - if (moveType == TYPE_ELECTRIC && GetMoveTarget(move) != MOVE_TARGET_ALL_BATTLERS) + if (moveType == TYPE_ELECTRIC && GetBattlerMoveTargetType(battlerAtk, move) != MOVE_TARGET_ALL_BATTLERS) effect = MOVE_ABSORBED_BY_DRAIN_HP_ABILITY; break; case ABILITY_WATER_ABSORB: @@ -4317,14 +4347,14 @@ bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 effect = MOVE_ABSORBED_BY_DRAIN_HP_ABILITY; break; case ABILITY_MOTOR_DRIVE: - if (moveType == TYPE_ELECTRIC && GetMoveTarget(move) != MOVE_TARGET_ALL_BATTLERS) // Potential bug in singles (might be solved with simu hp reudction) + if (moveType == TYPE_ELECTRIC && GetBattlerMoveTargetType(battlerAtk, move) != MOVE_TARGET_ALL_BATTLERS) { effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; statId = STAT_SPEED; } break; case ABILITY_LIGHTNING_ROD: - if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5 && moveType == TYPE_ELECTRIC && GetMoveTarget(move) != MOVE_TARGET_ALL_BATTLERS) // Potential bug in singles (might be solved with simu hp reudction) + if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5 && moveType == TYPE_ELECTRIC && GetBattlerMoveTargetType(battlerAtk, move) != MOVE_TARGET_ALL_BATTLERS) { effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; statId = STAT_SPATK; @@ -4365,7 +4395,7 @@ bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 break; } - if (effect == MOVE_ABSORBED_BY_NO_ABILITY || option == ABILITY_CHECK_TRIGGER) + if (effect == MOVE_ABSORBED_BY_NO_ABILITY || option != ABILITY_RUN_SCRIPT) return effect; switch (effect) @@ -4440,6 +4470,9 @@ bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 if (battleScript != NULL) { gMultiHitCounter = 0; // Prevent multi-hit moves from hitting more than once after move has been absorbed. + gLastUsedAbility = abilityDef; + RecordAbilityBattle(battlerDef, abilityDef); + gBattleScripting.battler = gBattlerAbility = battlerDef; gBattlescriptCurrInstr = battleScript; } @@ -5578,37 +5611,6 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } } break; - case ABILITYEFFECT_WOULD_BLOCK: - effect = CanAbilityBlockMove(gBattlerAttacker, battler, move, gLastUsedAbility, ABILITY_CHECK_TRIGGER); - if (effect && gLastUsedAbility != 0xFFFF) - RecordAbilityBattle(battler, gLastUsedAbility); - return effect; - case ABILITYEFFECT_MOVES_BLOCK: - effect = CanAbilityBlockMove(gBattlerAttacker, battler, move, gLastUsedAbility, ABILITY_RUN_SCRIPT); - - // prankster check - if (effect == 0 - && GetChosenMovePriority(gBattlerAttacker) > 0 - && BlocksPrankster(move, gBattlerAttacker, gBattlerTarget, TRUE) - && !(IsBattleMoveStatus(move) && (gLastUsedAbility == ABILITY_MAGIC_BOUNCE || gProtectStructs[gBattlerTarget].bounceMove))) - { - if (!IsDoubleBattle() - || !(GetBattlerMoveTargetType(gBattlerAttacker, move) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY))) - CancelMultiTurnMoves(gBattlerAttacker); // Don't cancel moves that can hit two targets bc one target might not be protected - gBattleScripting.battler = gBattlerAbility = gBattlerTarget; - gBattlescriptCurrInstr = BattleScript_DarkTypePreventsPrankster; - effect = 1; - } - break; - case ABILITYEFFECT_WOULD_ABSORB: - effect = CanAbilityAbsorbMove(gBattlerAttacker, battler, gLastUsedAbility, move, moveType, ABILITY_CHECK_TRIGGER); - gBattleStruct->pledgeMove = FALSE; - if (effect && gLastUsedAbility != 0xFFFF) - RecordAbilityBattle(battler, gLastUsedAbility); - return effect; - case ABILITYEFFECT_ABSORBING: - effect = CanAbilityAbsorbMove(gBattlerAttacker, battler, gLastUsedAbility, move, moveType, ABILITY_RUN_SCRIPT); - break; case ABILITYEFFECT_MOVE_END: // Think contact abilities. switch (gLastUsedAbility) { @@ -8798,7 +8800,7 @@ bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) isProtected = TRUE; else if (gProtectStructs[battlerDef].protected == PROTECT_KINGS_SHIELD && !IsBattleMoveStatus(move)) isProtected = TRUE; - else if (IsSideProtected(battlerDef, PROTECT_QUICK_GUARD) && GetChosenMovePriority(battlerAtk) > 0) + else if (IsSideProtected(battlerDef, PROTECT_QUICK_GUARD) && GetChosenMovePriority(battlerAtk, GetBattlerAbility(battlerAtk)) > 0) isProtected = TRUE; else if (IsSideProtected(battlerDef, PROTECT_MAT_BLOCK) && !IsBattleMoveStatus(move)) isProtected = TRUE; @@ -12205,18 +12207,21 @@ bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef) && battlerDef != BATTLE_PARTNER(battlerAtk)); } -static inline bool32 DoesBattlerHaveAbilityImmunity(u32 battlerDef) +static inline bool32 DoesBattlerHaveAbilityImmunity(u32 battlerAtk, u32 battlerDef, u32 moveType) { - return (AbilityBattleEffects(ABILITYEFFECT_WOULD_BLOCK, battlerDef, 0, 0, 0) - || AbilityBattleEffects(ABILITYEFFECT_WOULD_ABSORB, battlerDef, 0, 0, 0)); + u32 abilityDef = GetBattlerAbility(battlerDef); + + return CanAbilityBlockMove(battlerAtk, battlerDef, GetBattlerAbility(battlerAtk), abilityDef, gCurrentMove, ABILITY_CHECK_TRIGGER) + || CanAbilityAbsorbMove(battlerAtk, battlerDef, abilityDef, gCurrentMove, moveType, ABILITY_CHECK_TRIGGER); } bool32 TargetFullyImmuneToCurrMove(u32 battlerAtk, u32 battlerDef) { - return ((CalcTypeEffectivenessMultiplier(gCurrentMove, GetBattleMoveType(gCurrentMove), battlerAtk, battlerDef, GetBattlerAbility(battlerDef), FALSE) == UQ_4_12(0.0)) + u32 moveType = GetBattleMoveType(gCurrentMove); + return ((CalcTypeEffectivenessMultiplier(gCurrentMove, moveType, battlerAtk, battlerDef, GetBattlerAbility(battlerDef), FALSE) == UQ_4_12(0.0)) || IsBattlerProtected(battlerAtk, battlerDef, gCurrentMove) || IsSemiInvulnerable(battlerDef, gCurrentMove) - || DoesBattlerHaveAbilityImmunity(battlerDef)); + || DoesBattlerHaveAbilityImmunity(battlerAtk, battlerDef, moveType)); } u32 GetBattleMoveType(u32 move) diff --git a/test/battle/ability/lightning_rod.c b/test/battle/ability/lightning_rod.c index f2e9e434ce..ab2f0d89a0 100644 --- a/test/battle/ability/lightning_rod.c +++ b/test/battle/ability/lightning_rod.c @@ -97,3 +97,21 @@ DOUBLE_BATTLE_TEST("Lightning Rod redirects an ally's attack") } } } + +DOUBLE_BATTLE_TEST("Lightning Rod absorbs moves that targets all battlers but does not redirect") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_LIGHTNING_ROD); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_DISCHARGE); } + } SCENE { + NOT HP_BAR(opponentRight); + ABILITY_POPUP(opponentRight, ABILITY_LIGHTNING_ROD); + HP_BAR(opponentLeft); + HP_BAR(playerRight); + } +} diff --git a/test/battle/ability/motor_drive.c b/test/battle/ability/motor_drive.c index 83dc2a37e1..7ee02328ee 100644 --- a/test/battle/ability/motor_drive.c +++ b/test/battle/ability/motor_drive.c @@ -12,3 +12,21 @@ SINGLE_BATTLE_TEST("Motor Drive absorbs status moves") ABILITY_POPUP(opponent, ABILITY_MOTOR_DRIVE); } } + +DOUBLE_BATTLE_TEST("Motor Drive absorbs moves that target all battlers but does not redirect") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EMOLGA) { Ability(ABILITY_MOTOR_DRIVE); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_DISCHARGE); } + } SCENE { + HP_BAR(opponentLeft); + HP_BAR(playerRight); + NOT HP_BAR(opponentRight); + ABILITY_POPUP(opponentRight, ABILITY_MOTOR_DRIVE); + } +}