Refactors Move absorb / block function calls to remove redundancy (#6490)
Co-authored-by: Bassoonian <iasperbassoonian@gmail.com>
This commit is contained in:
parent
bbe8bbc45b
commit
7bf156be0e
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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..
|
||||
}
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user