Refactors Move absorb / block function calls to remove redundancy (#6490)

Co-authored-by: Bassoonian <iasperbassoonian@gmail.com>
This commit is contained in:
Alex 2025-04-08 14:26:18 +02:00 committed by GitHub
parent bbe8bbc45b
commit 7bf156be0e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 184 additions and 125 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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..
}

View File

@ -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))

View File

@ -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;

View File

@ -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(

View File

@ -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;

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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);
}
}