Adds CancelerSetTargets and improves Pre Attack Effects (#9178)
Some checks failed
CI / build (push) Has been cancelled
CI / docs_validate (push) Has been cancelled
CI / allcontributors (push) Has been cancelled

This commit is contained in:
PhallenTree 2026-02-14 19:54:01 +00:00 committed by GitHub
parent 5aed923f0d
commit cf18d086b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 615 additions and 481 deletions

View File

@ -557,7 +557,6 @@ BattleScript_SkyDropFlyingAlreadyConfused:
BattleScript_EffectFling::
attackcanceler
setlastuseditem BS_ATTACKER
accuracycheck BattleScript_FlingMissed
pause B_WAIT_TIME_SHORT
printstring STRINGID_PKMNFLUNG
@ -2239,6 +2238,7 @@ BattleScript_EffectHit::
attackcanceler
BattleScript_HitFromAccCheck::
accuracycheck BattleScript_MoveMissedPause
copybyte gEffectBattler, gBattlerAttacker
setpreattackadditionaleffect
BattleScript_HitFromDamageCalc::
damagecalc
@ -2249,6 +2249,7 @@ BattleScript_MoveEnd::
BattleScript_EffectHit_RetFromAccCheck::
accuracycheck BattleScript_MoveMissedPause
copybyte gEffectBattler, gBattlerAttacker
setpreattackadditionaleffect
damagecalc
BattleScript_Hit_RetFromAtkAnimation::

View File

@ -700,8 +700,8 @@ struct BattleStruct
u16 flingItem;
u8 incrementEchoedVoice:1;
u8 echoedVoiceCounter:3;
u8 preAttackAnimPlayed:1;
u8 padding4:1;
u8 attackAnimPlayed:1;
u8 preAttackEffectHappened:1;
u8 magicCoatActive:1;
u8 magicBounceActive:1;
u8 moveBouncer;

View File

@ -181,7 +181,6 @@ bool32 EndOrContinueWeather(void);
enum DamageCategory GetReflectDamageMoveDamageCategory(enum BattlerId battler, enum Move move);
bool32 IsUnnerveBlocked(enum BattlerId battler, enum Item itemId);
bool32 IsAffectedByFollowMe(enum BattlerId battlerAtk, enum BattleSide defSide, enum Move move);
void DetermineTarget(enum MoveTarget moveTarget, bool32 overwriteTarget);
void HandleAction_UseMove(void);
void HandleAction_Switch(void);
void HandleAction_UseItem(void);

View File

@ -40,19 +40,21 @@ enum CancelerState
CANCELER_GHOST,
CANCELER_PARALYZED,
CANCELER_INFATUATION,
CANCELER_BIDE,
CANCELER_Z_MOVES,
CANCELER_CHOICE_LOCK,
CANCELER_CALLSUBMOVE,
CANCELER_THAW,
CANCELER_STANCE_CHANGE_2,
CANCELER_ATTACKSTRING,
CANCELER_SET_TARGETS,
CANCELER_PPDEDUCTION,
CANCELER_MOVE_SPECIFIC_MESSAGE,
CANCELER_SKY_BATTLE,
CANCELER_WEATHER_PRIMAL,
CANCELER_FOCUS_PRE_GEN5,
CANCELER_BIDE,
CANCELER_MOVE_FAILURE,
CANCELER_MOVE_EFFECT_FAILURE_TARGET,
CANCELER_POWDER_STATUS,
CANCELER_PRIORITY_BLOCK,
CANCELER_PROTEAN,

View File

@ -452,37 +452,6 @@ static enum CancelerResult CancelerInfatuation(struct BattleContext *ctx)
return CANCELER_RESULT_SUCCESS;
}
static enum CancelerResult CancelerBide(struct BattleContext *ctx)
{
if (gBattleMons[ctx->battlerAtk].volatiles.bideTurns)
{
if (--gBattleMons[ctx->battlerAtk].volatiles.bideTurns)
{
gBattlescriptCurrInstr = BattleScript_BideStoringEnergy;
}
else
{
// This is removed in FRLG and Emerald for some reason
//gBattleMons[gBattlerAttacker].volatiles.multipleTurns = FALSE;
if (gBideDmg[ctx->battlerAtk])
{
gCurrentMove = MOVE_BIDE;
gBattlerTarget = gBideTarget[ctx->battlerAtk];
if (!IsBattlerAlive(ctx->battlerDef))
gBattlerTarget = GetBattleMoveTarget(MOVE_BIDE, TARGET_SELECTED);
gBattlescriptCurrInstr = BattleScript_BideAttack;
return CANCELER_RESULT_BREAK; // Jumps to a different script but no failure
}
else
{
gBattlescriptCurrInstr = BattleScript_BideNoEnergyToAttack;
return CANCELER_RESULT_FAILURE;
}
}
}
return CANCELER_RESULT_SUCCESS;
}
static enum CancelerResult CancelerZMoves(struct BattleContext *ctx)
{
if (GetActiveGimmick(ctx->battlerAtk) == GIMMICK_Z_MOVE)
@ -532,7 +501,7 @@ static enum CancelerResult CancelerCallSubmove(struct BattleContext *ctx)
const u8 *battleScript = NULL;
battleScript = BattleScript_SubmoveAttackstring;
switch(GetMoveEffect(ctx->move))
switch (GetMoveEffect(ctx->move))
{
case EFFECT_MIRROR_MOVE:
calledMove = GetMirrorMoveMove();
@ -578,6 +547,8 @@ static enum CancelerResult CancelerCallSubmove(struct BattleContext *ctx)
gBattleStruct->submoveAnnouncement = SUBMOVE_SUCCESS;
gCalledMove = calledMove;
if (GetBattlerMoveTargetType(ctx->battlerAtk, ctx->move) == TARGET_DEPENDS) // originally using a move without a set target
gBattlerTarget = GetBattleMoveTarget(calledMove, TARGET_NONE);
BattleScriptCall(battleScript);
return CANCELER_RESULT_BREAK;
}
@ -643,10 +614,314 @@ static enum CancelerResult CancelerAttackstring(struct BattleContext *ctx)
return CANCELER_RESULT_BREAK;
}
#define checkFailure TRUE
#define skipFailure FALSE
static bool32 IsSingleTarget(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (battlerDef != gBattlerTarget)
return skipFailure;
return checkFailure;
}
static bool32 IsSmartTarget(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (!IsBattlerAlly(gBattlerTarget, battlerDef) || battlerAtk == battlerDef)
return skipFailure;
return checkFailure;
}
static bool32 IsTargetingBothFoes(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (battlerDef == BATTLE_PARTNER(battlerAtk) || battlerAtk == battlerDef)
{
// Because of Magic Bounce and Magic Coat we don't want to set MOVE_RESULT_NO_EFFECT
if (GetMoveCategory(gCurrentMove) != DAMAGE_CATEGORY_STATUS)
gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_NO_EFFECT;
return skipFailure;
}
return checkFailure;
}
static bool32 IsTargetingSelf(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
return skipFailure;
}
static bool32 IsTargetingAlly(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (battlerDef != BATTLE_PARTNER(battlerAtk))
{
gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_NO_EFFECT;
return skipFailure;
}
return checkFailure;
}
static bool32 IsTargetingSelfAndAlly(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (battlerDef != BATTLE_PARTNER(battlerAtk))
{
if (battlerDef != battlerAtk) // Don't set result flags for user
gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_NO_EFFECT;
return skipFailure;
}
return checkFailure;
}
static bool32 IsTargetingSelfOrAlly(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (battlerDef == battlerAtk)
return skipFailure;
if (battlerDef != BATTLE_PARTNER(battlerAtk))
{
gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_NO_EFFECT;
return skipFailure;
}
return checkFailure;
}
static bool32 IsTargetingFoesAndAlly(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (battlerAtk == battlerDef)
return skipFailure; // Don't set result flags for user
return checkFailure;
}
static bool32 IsTargetingField(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
return skipFailure;
}
static bool32 IsTargetingOpponentsField(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (IsBattlerAlly(battlerDef, BATTLE_OPPOSITE(battlerAtk)))
return checkFailure;
return skipFailure;
}
static bool32 IsTargetingAllBattlers(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
return checkFailure;
}
// ShouldCheckFailureOnTarget
static bool32 (*const sShouldCheckTargetMoveFailure[])(enum BattlerId battlerAtk, enum BattlerId battlerDef) =
{
[TARGET_NONE] = IsTargetingField,
[TARGET_SELECTED] = IsSingleTarget,
[TARGET_DEPENDS] = IsSingleTarget,
[TARGET_OPPONENT] = IsSingleTarget,
[TARGET_RANDOM] = IsSingleTarget,
[TARGET_BOTH] = IsTargetingBothFoes,
[TARGET_USER] = IsTargetingSelf,
[TARGET_SMART] = IsSmartTarget,
[TARGET_ALLY] = IsTargetingAlly,
[TARGET_USER_AND_ALLY] = IsTargetingSelfAndAlly,
[TARGET_USER_OR_ALLY] = IsTargetingSelfOrAlly,
[TARGET_FOES_AND_ALLY] = IsTargetingFoesAndAlly,
[TARGET_FIELD] = IsTargetingField,
[TARGET_OPPONENTS_FIELD] = IsTargetingOpponentsField,
[TARGET_ALL_BATTLERS] = IsTargetingAllBattlers,
};
static bool32 ShouldCheckTargetMoveFailure(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Move move, enum MoveTarget moveTarget)
{
// For Bounced moves
if (IsBattlerUnaffectedByMove(battlerDef))
return skipFailure;
return sShouldCheckTargetMoveFailure[moveTarget](battlerAtk, battlerDef);
}
#undef checkFailure
#undef skipFailure
static inline bool32 IsSmartTargetMoveConsecutiveHit(enum BattlerId battlerAtk, enum Move move)
{
if (GetBattlerMoveTargetType(battlerAtk, move) != TARGET_SMART)
return FALSE;
if (gMultiHitCounter < GetMoveStrikeCount(move))
return TRUE;
return FALSE;
}
bool32 IsAffectedByFollowMe(enum BattlerId battlerAtk, enum BattleSide defSide, enum Move move)
{
enum Ability ability = GetBattlerAbility(battlerAtk);
enum BattleMoveEffects effect = GetMoveEffect(move);
if (gSideTimers[defSide].followmeTimer == 0
|| (!IsBattlerAlive(gSideTimers[defSide].followmeTarget) && !IsSmartTargetMoveConsecutiveHit(battlerAtk, move))
|| effect == EFFECT_SNIPE_SHOT
|| effect == EFFECT_SKY_DROP
|| IsAbilityAndRecord(battlerAtk, ability, ABILITY_PROPELLER_TAIL)
|| IsAbilityAndRecord(battlerAtk, ability, ABILITY_STALWART))
return FALSE;
if (effect == EFFECT_PURSUIT && IsPursuitTargetSet())
return FALSE;
if (gSideTimers[defSide].followmePowder && !IsAffectedByPowderMove(battlerAtk, ability, GetBattlerHoldEffect(battlerAtk)))
return FALSE;
return TRUE;
}
static bool32 HandleMoveTargetRedirection(enum MoveTarget moveTarget)
{
u32 redirectorOrderNum = MAX_BATTLERS_COUNT;
enum BattleMoveEffects moveEffect = GetMoveEffect(gCurrentMove);
enum BattleSide side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker));
if (moveEffect == EFFECT_REFLECT_DAMAGE)
{
enum DamageCategory reflectCategory = GetReflectDamageMoveDamageCategory(gBattlerAttacker, gCurrentMove);
if (reflectCategory == DAMAGE_CATEGORY_PHYSICAL)
gBattlerTarget = gBattleStruct->moveTarget[gBattlerAttacker] = gProtectStructs[gBattlerAttacker].physicalBattlerId;
else
gBattlerTarget = gBattleStruct->moveTarget[gBattlerAttacker] = gProtectStructs[gBattlerAttacker].specialBattlerId;
}
if (IsAffectedByFollowMe(gBattlerAttacker, side, gCurrentMove)
&& (moveTarget == TARGET_SELECTED || moveTarget == TARGET_SMART || moveEffect == EFFECT_REFLECT_DAMAGE)
&& !IsBattlerAlly(gBattlerAttacker, gSideTimers[side].followmeTarget))
{
gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = gSideTimers[side].followmeTarget; // follow me moxie fix
return TRUE;
}
enum Type moveType = GetBattleMoveType(gCurrentMove);
enum Ability ability = GetBattlerAbility(gBattlerTarget);
bool32 currTargetCantAbsorb = ((ability != ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC)
|| (ability != ABILITY_STORM_DRAIN && moveType == TYPE_WATER));
if (currTargetCantAbsorb
&& IsDoubleBattle()
&& gSideTimers[side].followmeTimer == 0
&& moveTarget != TARGET_USER
&& moveTarget != TARGET_ALL_BATTLERS
&& moveTarget != TARGET_FIELD
&& moveEffect != EFFECT_TEATIME
&& moveEffect != EFFECT_SNIPE_SHOT
&& moveEffect != EFFECT_PLEDGE)
{
// Find first battler that redirects the move (in turn order)
enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker);
enum BattlerId battler;
for (battler = 0; battler < gBattlersCount; battler++)
{
ability = GetBattlerAbility(battler);
if ((B_REDIRECT_ABILITY_ALLIES >= GEN_4 || !IsBattlerAlly(gBattlerAttacker, battler))
&& battler != gBattlerAttacker
&& gBattlerTarget != battler
&& ((ability == ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC)
|| (ability == ABILITY_STORM_DRAIN && moveType == TYPE_WATER))
&& GetBattlerTurnOrderNum(battler) < redirectorOrderNum
&& !IsAbilityAndRecord(gBattlerAttacker, abilityAtk, ABILITY_PROPELLER_TAIL)
&& !IsAbilityAndRecord(gBattlerAttacker, abilityAtk, ABILITY_STALWART))
{
redirectorOrderNum = GetBattlerTurnOrderNum(battler);
}
}
if (redirectorOrderNum != MAX_BATTLERS_COUNT)
{
enum Ability battlerAbility;
battler = gBattlerByTurnOrder[redirectorOrderNum];
battlerAbility = GetBattlerAbility(battler);
RecordAbilityBattle(battler, battlerAbility);
gSpecialStatuses[battler].abilityRedirected = TRUE;
gBattlerTarget = battler;
return TRUE;
}
}
return FALSE;
}
static bool32 WasOriginalTargetAlly(enum MoveTarget moveTarget)
{
if (!gProtectStructs[BATTLE_PARTNER(gBattlerAttacker)].usedAllySwitch)
return FALSE;
if ((moveTarget == TARGET_ALLY || moveTarget == TARGET_USER_OR_ALLY) && gBattlerAttacker == gBattlerTarget)
return TRUE;
return FALSE;
}
static enum CancelerResult CancelerSetTargets(struct BattleContext *ctx)
{
enum MoveTarget moveTarget = GetBattlerMoveTargetType(ctx->battlerAtk, ctx->move);
if (!HandleMoveTargetRedirection(moveTarget))
{
if (IsDoubleBattle() && moveTarget == TARGET_RANDOM)
{
gBattlerTarget = SetRandomTarget(gBattlerAttacker);
if (gAbsentBattlerFlags & (1u << gBattlerTarget)
&& !IsBattlerAlly(gBattlerAttacker, gBattlerTarget))
{
gBattlerTarget = GetPartnerBattler(gBattlerTarget);
}
}
else if (moveTarget == TARGET_ALLY && !IsBattlerAlly(gBattlerTarget, gBattlerAttacker))
{
gBattlerTarget = BATTLE_PARTNER(gBattlerAttacker);
}
else if (IsDoubleBattle() && moveTarget == TARGET_FOES_AND_ALLY)
{
for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++)
{
if (gBattlerTarget == gBattlerAttacker)
continue;
if (IsBattlerAlive(gBattlerTarget))
break;
}
}
else if (moveTarget == TARGET_USER || moveTarget == TARGET_USER_AND_ALLY)
{
gBattlerTarget = gBattlerAttacker;
}
else if (!IsBattlerAlive(gBattlerTarget)
&& moveTarget != TARGET_OPPONENTS_FIELD
&& IsDoubleBattle()
&& (!IsBattlerAlly(gBattlerAttacker, gBattlerTarget)))
{
gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget)));
}
}
while (gBattleStruct->eventState.atkCancelerBattler < gBattlersCount)
{
enum BattlerId battlerDef = gBattleStruct->eventState.atkCancelerBattler++;
if (!ShouldCheckTargetMoveFailure(ctx->battlerAtk, battlerDef, ctx->move, moveTarget))
gBattleStruct->battlerState[ctx->battlerAtk].targetsDone[battlerDef] = TRUE;
}
gBattleStruct->eventState.atkCancelerBattler = 0;
if (IsBattlerAlly(gBattlerAttacker, gBattlerTarget) && !IsBattlerAlive(gBattlerTarget))
{
gBattlescriptCurrInstr = BattleScript_ButItFailed;
return CANCELER_RESULT_FAILURE;
}
else if (WasOriginalTargetAlly(moveTarget))
{
gBattlescriptCurrInstr = BattleScript_ButItFailed;
return CANCELER_RESULT_FAILURE;
}
return CANCELER_RESULT_BREAK; // update ctx->battlerDef
}
static enum CancelerResult CancelerPPDeduction(struct BattleContext *ctx)
{
if (gBattleMons[ctx->battlerAtk].volatiles.multipleTurns
|| gSpecialStatuses[ctx->battlerAtk].dancerUsedMove
|| gBattleStruct->bouncedMoveIsUsed
|| ctx->move == MOVE_STRUGGLE)
return CANCELER_RESULT_SUCCESS;
@ -711,13 +986,11 @@ static enum CancelerResult CancelerPPDeduction(struct BattleContext *ctx)
else
{
gBattleStruct->submoveAnnouncement = SUBMOVE_NO_EFFECT;
gBattlerTarget = GetBattleMoveTarget(ctx->move, TARGET_NONE);
gBattleScripting.animTurn = 0;
gBattleScripting.animTargetsHit = 0;
// Possibly better to just move type setting and redirection to attackcanceler as a new case at this point
SetTypeBeforeUsingMove(ctx->move, ctx->battlerAtk);
DetermineTarget(moveTarget, FALSE);
ClearDamageCalcResults();
gBattlescriptCurrInstr = GetMoveBattleScript(ctx->move);
return CANCELER_RESULT_BREAK;
@ -767,12 +1040,61 @@ static enum CancelerResult CancelerWeatherPrimal(struct BattleContext *ctx)
return result;
}
static bool32 ShouldSkipFailureCheckOnBattler(enum BattlerId battlerAtk, enum BattlerId battlerDef, bool32 checkResultFlag)
{
if (gBattleStruct->battlerState[battlerAtk].targetsDone[battlerDef])
return TRUE;
if (checkResultFlag && gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NO_EFFECT)
return TRUE;
if (GetConfig(CONFIG_CHECK_USER_FAILURE) >= GEN_5 && battlerAtk == battlerDef)
return TRUE;
return FALSE;
}
static enum CancelerResult CancelerBide(struct BattleContext *ctx)
{
if (gBattleMons[ctx->battlerAtk].volatiles.bideTurns)
{
if (--gBattleMons[ctx->battlerAtk].volatiles.bideTurns)
{
gBattlescriptCurrInstr = BattleScript_BideStoringEnergy;
}
else
{
if (gBideDmg[ctx->battlerAtk])
{
gCurrentMove = MOVE_BIDE;
gBattlerTarget = gBideTarget[ctx->battlerAtk];
if (!IsBattlerAlive(gBattlerTarget))
gBattlerTarget = GetBattleMoveTarget(MOVE_BIDE, TARGET_SELECTED);
gBattlescriptCurrInstr = BattleScript_BideAttack;
}
else
{
gBattlescriptCurrInstr = BattleScript_BideNoEnergyToAttack;
return CANCELER_RESULT_FAILURE;
}
}
}
return CANCELER_RESULT_BREAK; // Jumps to a different script but no failure
}
static enum CancelerResult CancelerMoveFailure(struct BattleContext *ctx)
{
const u8 *battleScript = NULL;
switch (GetMoveEffect(ctx->move))
{
case EFFECT_FLING:
if (!CanFling(ctx->battlerAtk))
battleScript = BattleScript_ButItFailed;
else // set fling item
gBattleStruct->flingItem = gLastUsedItem = gBattleMons[ctx->battlerAtk].item;
break;
case EFFECT_POLTERGEIST:
if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM)
battleScript = BattleScript_ButItFailed;
break;
case EFFECT_FAIL_IF_NOT_ARG_TYPE:
if (!IS_BATTLER_OF_TYPE(ctx->battlerAtk, GetMoveArgType(ctx->move)))
battleScript = BattleScript_ButItFailed;
@ -821,32 +1143,16 @@ static enum CancelerResult CancelerMoveFailure(struct BattleContext *ctx)
if (!gBattleStruct->battlerState[ctx->battlerAtk].isFirstTurn || gSpecialStatuses[ctx->battlerAtk].instructedChosenTarget)
battleScript = BattleScript_ButItFailed;
break;
case EFFECT_FLING:
if (!CanFling(ctx->battlerAtk))
battleScript = BattleScript_ButItFailed;
else if (!IsBattlerAlive(ctx->battlerDef)) // Edge case for removing a mon's item when there is no target available after using Fling.
battleScript = BattleScript_FlingFailConsumeItem;
break;
case EFFECT_FOLLOW_ME:
if (B_UPDATED_MOVE_DATA >= GEN_8 && !(gBattleTypeFlags & BATTLE_TYPE_DOUBLE))
battleScript = BattleScript_ButItFailed;
break;
case EFFECT_FUTURE_SIGHT:
if (gBattleStruct->futureSight[ctx->battlerDef].counter > 0)
battleScript = BattleScript_ButItFailed;
break;
case EFFECT_LAST_RESORT:
if (!CanUseLastResort(ctx->battlerAtk))
battleScript = BattleScript_ButItFailed;
break;
case EFFECT_NO_RETREAT:
if (gBattleMons[ctx->battlerDef].volatiles.noRetreat)
battleScript = BattleScript_ButItFailed;
break;
case EFFECT_POLTERGEIST:
if (gBattleMons[ctx->battlerDef].item == ITEM_NONE
|| gFieldStatuses & STATUS_FIELD_MAGIC_ROOM
|| ctx->abilityDef == ABILITY_KLUTZ)
if (gBattleMons[ctx->battlerAtk].volatiles.noRetreat)
battleScript = BattleScript_ButItFailed;
break;
case EFFECT_PROTECT:
@ -886,21 +1192,6 @@ static enum CancelerResult CancelerMoveFailure(struct BattleContext *ctx)
|| ctx->abilityAtk == ABILITY_PURIFYING_SALT)
battleScript = BattleScript_InsomniaProtects;
break;
case EFFECT_SUCKER_PUNCH:
if (HasBattlerActedThisTurn(ctx->battlerDef)
|| (IsBattleMoveStatus(GetBattlerChosenMove(ctx->battlerDef)) && !gProtectStructs[ctx->battlerDef].noValidMoves))
battleScript = BattleScript_ButItFailed;
break;
case EFFECT_UPPER_HAND:
{
s32 prio = GetChosenMovePriority(ctx->battlerDef, GetBattlerAbility(ctx->battlerDef));
if (prio < 1 || prio > 3 // Fails if priority is less than 1 or greater than 3, if target already moved, or if using a status
|| HasBattlerActedThisTurn(ctx->battlerDef)
|| gChosenMoveByBattler[ctx->battlerDef] == MOVE_NONE
|| IsBattleMoveStatus(gChosenMoveByBattler[ctx->battlerDef]))
battleScript = BattleScript_ButItFailed;
break;
}
case EFFECT_SNORE:
if (!(gBattleMons[ctx->battlerAtk].status1 & STATUS1_SLEEP)
&& ctx->abilityAtk != ABILITY_COMATOSE)
@ -926,11 +1217,6 @@ static enum CancelerResult CancelerMoveFailure(struct BattleContext *ctx)
case EFFECT_TELEPORT:
// TODO: follow up: Can't make sense of teleport logic
break;
case EFFECT_LOW_KICK:
case EFFECT_HEAT_CRASH:
if (GetActiveGimmick(ctx->battlerDef) == GIMMICK_DYNAMAX)
battleScript = BattleScript_MoveBlockedByDynamax;
break;
case EFFECT_NATURAL_GIFT:
if (GetItemPocket(gBattleMons[ctx->battlerAtk].item) != POCKET_BERRIES
|| gFieldStatuses & STATUS_FIELD_MAGIC_ROOM
@ -951,6 +1237,113 @@ static enum CancelerResult CancelerMoveFailure(struct BattleContext *ctx)
return CANCELER_RESULT_SUCCESS;
}
static enum CancelerResult CancelerMoveEffectFailureTarget(struct BattleContext *ctx)
{
const u8 *battleScript = NULL;
u32 numAffectedTargets = 0;
while (gBattleStruct->eventState.atkCancelerBattler < gBattlersCount)
{
enum BattlerId battlerDef = gBattleStruct->eventState.atkCancelerBattler++;
if (ShouldSkipFailureCheckOnBattler(ctx->battlerAtk, battlerDef, TRUE))
continue;
switch (GetMoveEffect(ctx->move))
{
case EFFECT_FLING:
if (!IsBattlerAlive(battlerDef)) // Edge case for removing a mon's item when there is no target available after using Fling.
{
battleScript = BattleScript_FlingFailConsumeItem;
}
else
{
numAffectedTargets++;
continue;
}
break;
case EFFECT_FUTURE_SIGHT:
if (gBattleStruct->futureSight[battlerDef].counter > 0)
{
battleScript = BattleScript_ButItFailed;
}
else
{
numAffectedTargets++;
continue;
}
break;
case EFFECT_POLTERGEIST:
if (gBattleMons[battlerDef].item == ITEM_NONE
|| GetBattlerAbility(battlerDef) == ABILITY_KLUTZ)
{
battleScript = BattleScript_ButItFailed;
}
else
{
numAffectedTargets++;
continue;
}
break;
case EFFECT_SUCKER_PUNCH:
if (HasBattlerActedThisTurn(battlerDef)
|| (IsBattleMoveStatus(GetBattlerChosenMove(battlerDef)) && !gProtectStructs[battlerDef].noValidMoves))
{
battleScript = BattleScript_ButItFailed;
}
else
{
numAffectedTargets++;
continue;
}
break;
case EFFECT_UPPER_HAND:
{
s32 prio = GetChosenMovePriority(battlerDef, GetBattlerAbility(battlerDef));
if (prio < 1 || prio > 3 // Fails if priority is less than 1 or greater than 3, if target already moved, or if using a status
|| HasBattlerActedThisTurn(battlerDef)
|| gChosenMoveByBattler[battlerDef] == MOVE_NONE
|| IsBattleMoveStatus(gChosenMoveByBattler[battlerDef]))
{
battleScript = BattleScript_ButItFailed;
}
else
{
numAffectedTargets++;
continue;
}
break;
}
case EFFECT_LOW_KICK:
case EFFECT_HEAT_CRASH:
if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
{
battleScript = BattleScript_MoveBlockedByDynamax;
}
else
{
numAffectedTargets++;
continue;
}
break;
default:
continue;
}
gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_FAILED;
}
gBattleStruct->eventState.atkCancelerBattler = 0;
if (battleScript != NULL && numAffectedTargets == 0)
{
gBattlescriptCurrInstr = battleScript;
return CANCELER_RESULT_FAILURE;
}
return CANCELER_RESULT_SUCCESS;
}
static enum CancelerResult CancelerPowderStatus(struct BattleContext *ctx)
{
if (TryActivatePowderStatus(ctx->move))
@ -983,18 +1376,19 @@ static enum CancelerResult CancelerPriorityBlock(struct BattleContext *ctx)
{
bool32 effect = FALSE;
s32 priority = GetChosenMovePriority(ctx->battlerAtk, ctx->abilityAtk);
enum MoveTarget moveTarget = GetBattlerMoveTargetType(ctx->battlerAtk, ctx->move);
if (priority <= 0)
if (priority <= 0 || moveTarget == TARGET_FIELD || moveTarget == TARGET_OPPONENTS_FIELD)
return CANCELER_RESULT_SUCCESS;
enum BattlerId battler;
enum Ability ability = ABILITY_NONE; // ability of battler who is blocking
bool32 isSpreadMove = IsSpreadMove(GetBattlerMoveTargetType(ctx->battlerAtk, ctx->move));
for (battler = 0; battler < gBattlersCount; battler++)
{
if (!IsBattlerAlive(battler) || IsBattlerAlly(ctx->battlerAtk, battler))
continue;
if (!isSpreadMove && !IsBattlerAlly(battler, ctx->battlerDef))
if (ShouldSkipFailureCheckOnBattler(ctx->battlerAtk, battler, TRUE)
&& (!IsDoubleBattle() || ShouldSkipFailureCheckOnBattler(ctx->battlerAtk, BATTLE_PARTNER(battler), TRUE))) // either battler or partner is affected
continue;
ability = GetBattlerAbility(battler);
@ -1050,7 +1444,7 @@ static enum CancelerResult CancelerExplosion(struct BattleContext *ctx)
{
// KO user of Explosion; for Final Gambit doesn't happen if target is immune or if it missed
if (IsExplosionMove(ctx->move)
&& (GetMoveEffect(ctx->move) != EFFECT_FINAL_GAMBIT || !IsBattlerUnaffectedByMove(ctx->battlerDef)))
&& (GetMoveEffect(ctx->move) != EFFECT_FINAL_GAMBIT || IsAnyTargetAffected()))
{
BattleScriptCall(BattleScript_Explosion);
return CANCELER_RESULT_BREAK;
@ -1211,129 +1605,6 @@ static enum CancelerResult CancelerTookAttack(struct BattleContext *ctx)
return CANCELER_RESULT_SUCCESS;
}
#define checkFailure FALSE
#define skipFailure TRUE
static bool32 IsSingleTarget(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (battlerDef != gBattlerTarget)
return skipFailure;
return checkFailure;
}
static bool32 IsSmartTarget(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (battlerAtk == BATTLE_PARTNER(battlerDef))
return skipFailure;
return checkFailure;
}
static bool32 IsTargetingBothFoes(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (battlerDef == BATTLE_PARTNER(battlerAtk) || battlerAtk == battlerDef)
{
// Because of Magic Bounce and Magic Coat we don't want to set MOVE_RESULT_NO_EFFECT
if (GetMoveCategory(gCurrentMove) != DAMAGE_CATEGORY_STATUS)
gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_NO_EFFECT;
return skipFailure;
}
return checkFailure;
}
static bool32 IsTargetingSelf(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
return skipFailure;
}
static bool32 IsTargetingAlly(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (battlerDef != BATTLE_PARTNER(battlerAtk))
{
gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_NO_EFFECT;
return skipFailure;
}
return checkFailure;
}
static bool32 IsTargetingSelfAndAlly(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (battlerDef != BATTLE_PARTNER(battlerAtk))
{
if (battlerDef != battlerAtk) // Don't set result flags for user
gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_NO_EFFECT;
return skipFailure;
}
return checkFailure;
}
static bool32 IsTargetingSelfOrAlly(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (battlerDef == battlerAtk)
return skipFailure;
if (battlerDef != BATTLE_PARTNER(battlerAtk))
{
gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_NO_EFFECT;
return skipFailure;
}
return checkFailure;
}
static bool32 IsTargetingFoesAndAlly(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (battlerAtk == battlerDef)
return skipFailure; // Don't set result flags for user
return checkFailure;
}
static bool32 IsTargetingField(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
return skipFailure;
}
static bool32 IsTargetingOpponentsField(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
return checkFailure; // Bounce failure only
}
static bool32 IsTargetingAllBattlers(enum BattlerId battlerAtk, enum BattlerId battlerDef)
{
if (GetConfig(CONFIG_CHECK_USER_FAILURE) >= GEN_5 && battlerAtk == battlerDef)
return skipFailure;
return checkFailure;
}
static bool32 (*const sShouldCheckTargetMoveFailure[])(enum BattlerId battlerAtk, enum BattlerId battlerDef) =
{
[TARGET_NONE] = IsTargetingField,
[TARGET_SELECTED] = IsSingleTarget,
[TARGET_DEPENDS] = IsSingleTarget,
[TARGET_OPPONENT] = IsSingleTarget,
[TARGET_RANDOM] = IsSingleTarget,
[TARGET_BOTH] = IsTargetingBothFoes,
[TARGET_USER] = IsTargetingSelf,
[TARGET_SMART] = IsSmartTarget,
[TARGET_ALLY] = IsTargetingAlly,
[TARGET_USER_AND_ALLY] = IsTargetingSelfAndAlly,
[TARGET_USER_OR_ALLY] = IsTargetingSelfOrAlly,
[TARGET_FOES_AND_ALLY] = IsTargetingFoesAndAlly,
[TARGET_FIELD] = IsTargetingField,
[TARGET_OPPONENTS_FIELD] = IsTargetingOpponentsField,
[TARGET_ALL_BATTLERS] = IsTargetingAllBattlers,
};
static bool32 ShouldCheckTargetMoveFailure(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Move move, enum MoveTarget moveTarget)
{
// For Bounced moves
if (IsBattlerUnaffectedByMove(battlerDef))
return skipFailure;
return sShouldCheckTargetMoveFailure[moveTarget](battlerAtk, battlerDef);
}
#undef checkFailure
#undef skipFailure
static enum CancelerResult CancelerTargetFailure(struct BattleContext *ctx)
{
bool32 targetAvoidedAttack = FALSE;
@ -1347,7 +1618,7 @@ static enum CancelerResult CancelerTargetFailure(struct BattleContext *ctx)
{
ctx->battlerDef = gBattleStruct->eventState.atkCancelerBattler++;
if (ShouldCheckTargetMoveFailure(ctx->battlerAtk, ctx->battlerDef, ctx->move, moveTarget))
if (ShouldSkipFailureCheckOnBattler(ctx->battlerAtk, ctx->battlerDef, FALSE))
continue;
ctx->abilityDef = GetBattlerAbility(ctx->battlerDef);
@ -1360,7 +1631,12 @@ static enum CancelerResult CancelerTargetFailure(struct BattleContext *ctx)
continue;
}
if (!IsBattlerAlive(ctx->battlerDef))
if (IsBattlerUnaffectedByMove(ctx->battlerDef)) // immune but targeted
{
BattleScriptCall(BattleScript_DoesntAffectScripting);
targetAvoidedAttack = TRUE;
}
else if (!IsBattlerAlive(ctx->battlerDef))
{
gBattleStruct->moveResultFlags[ctx->battlerDef] |= MOVE_RESULT_FAILED;
continue;
@ -1392,7 +1668,6 @@ static enum CancelerResult CancelerTargetFailure(struct BattleContext *ctx)
}
else if (CanMoveBeBlockedByTarget(ctx, movePriority))
{
gBattleStruct->moveResultFlags[ctx->battlerDef] |= MOVE_RESULT_MISSED;
targetAvoidedAttack = TRUE;
}
@ -1464,6 +1739,9 @@ static enum CancelerResult CancelerNotFullyProtected(struct BattleContext *ctx)
{
enum BattlerId battlerDef = gBattleStruct->eventState.atkCancelerBattler++;
if (ShouldSkipFailureCheckOnBattler(ctx->battlerAtk, battlerDef, TRUE))
continue;
if (CantFullyProtectFromMove(battlerDef))
{
BattleScriptCall(BattleScript_CouldntFullyProtect);
@ -1615,18 +1893,20 @@ static enum CancelerResult (*const sMoveSuccessOrderCancelers[])(struct BattleCo
[CANCELER_GHOST] = CancelerGhost,
[CANCELER_PARALYZED] = CancelerParalyzed,
[CANCELER_INFATUATION] = CancelerInfatuation,
[CANCELER_BIDE] = CancelerBide,
[CANCELER_Z_MOVES] = CancelerZMoves,
[CANCELER_CHOICE_LOCK] = CancelerChoiceLock,
[CANCELER_CALLSUBMOVE] = CancelerCallSubmove,
[CANCELER_THAW] = CancelerThaw,
[CANCELER_STANCE_CHANGE_2] = CancelerStanceChangeTwo,
[CANCELER_ATTACKSTRING] = CancelerAttackstring,
[CANCELER_SET_TARGETS] = CancelerSetTargets,
[CANCELER_PPDEDUCTION] = CancelerPPDeduction,
[CANCELER_SKY_BATTLE] = CancelerSkyBattle,
[CANCELER_WEATHER_PRIMAL] = CancelerWeatherPrimal,
[CANCELER_FOCUS_PRE_GEN5] = CancelerFocusPreGen5,
[CANCELER_BIDE] = CancelerBide,
[CANCELER_MOVE_FAILURE] = CancelerMoveFailure,
[CANCELER_MOVE_EFFECT_FAILURE_TARGET] = CancelerMoveEffectFailureTarget,
[CANCELER_POWDER_STATUS] = CancelerPowderStatus,
[CANCELER_PRIORITY_BLOCK] = CancelerPriorityBlock,
[CANCELER_PROTEAN] = CancelerProtean,
@ -2102,6 +2382,7 @@ static enum MoveEndResult MoveEndFaintBlock(void)
break;
case FAINT_BLOCK_CHECK_TARGET_FAINTED: // Stop if target already ran the block / is alive or absent
if (IsBattlerAlive(gBattlerTarget)
|| (gAbsentBattlerFlags & 1u << gBattlerTarget)
|| gBattleStruct->battlerState[gBattlerTarget].fainted)
{
gBattleScripting.moveendState++;
@ -2452,6 +2733,7 @@ static enum MoveEndResult MoveEndMultihitMove(void)
&& gMultiHitCounter)
{
enum MoveTarget target = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
gBattleStruct->preAttackEffectHappened = FALSE;
gMultiHitCounter--;
if (!IsBattlerAlive(gBattlerTarget) && target != TARGET_SMART)
gMultiHitCounter = 0;
@ -3273,7 +3555,7 @@ static bool32 ShouldSetStompingTantrumTimer(void)
if (!IsDoubleSpreadMove())
return gBattleStruct->moveResultFlags[gBattlerTarget] & (MOVE_RESULT_FAILED | MOVE_RESULT_DOESNT_AFFECT_FOE);
for (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
for (enum BattlerId battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
if (gBattlerAttacker == battlerDef)
continue;
@ -3322,6 +3604,7 @@ static enum MoveEndResult MoveEndClearBits(void)
gBattleStruct->fickleBeamBoosted = FALSE;
gBattleStruct->battlerState[gBattlerAttacker].usedMicleBerry = FALSE;
gBattleStruct->toxicChainPriority = FALSE;
gBattleStruct->flingItem = ITEM_NONE;
if (gBattleStruct->unableToUseMove)
gBattleStruct->pledgeMove = FALSE;
if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE)

View File

@ -352,6 +352,7 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u
static void ResetValuesForCalledMove(void);
static bool32 CanAbilityShieldActivateForBattler(enum BattlerId battler);
static void PlayAnimation(enum BattlerId battler, u8 animId, const u16 *argPtr, const u8 *nextInstr);
static u32 GetPossibleNextTarget(u32 currTarget);
static void Cmd_attackcanceler(void);
static void Cmd_accuracycheck(void);
@ -939,6 +940,7 @@ bool32 ProteanTryChangeType(enum BattlerId battler, enum Ability ability, enum M
{
if ((ability == ABILITY_PROTEAN || ability == ABILITY_LIBERO)
&& !gBattleMons[gBattlerAttacker].volatiles.usedProteanLibero
&& !gBattleStruct->bouncedMoveIsUsed
&& (gBattleMons[battler].types[0] != moveType || gBattleMons[battler].types[1] != moveType
|| (gBattleMons[battler].types[2] != moveType && gBattleMons[battler].types[2] != TYPE_MYSTERY))
&& move != MOVE_STRUGGLE
@ -1561,7 +1563,7 @@ static void Cmd_attackanimation(void)
moveResultFlags = UpdateEffectivenessResultFlagsForDoubleSpreadMoves(gBattleStruct->moveResultFlags[gBattlerTarget]);
bool32 isAnimDisabled = (gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION)
|| gBattleStruct->preAttackAnimPlayed);
|| gBattleStruct->attackAnimPlayed);
if (isAnimDisabled
&& effect != EFFECT_TRANSFORM
&& effect != EFFECT_SUBSTITUTE
@ -3493,7 +3495,7 @@ void SetMoveEffect(enum BattlerId battlerAtk, enum BattlerId effectBattler, enum
gSideStatuses[i] &= ~SIDE_STATUS_SCREEN_ANY;
gBattleScripting.animTurn = 1;
gBattleScripting.animTargetsHit = 1;
gBattleStruct->preAttackAnimPlayed = TRUE; // The whole brick break animation is covered by the move so don't play twice
gBattleStruct->attackAnimPlayed = TRUE; // The whole brick break animation is covered by the move so don't play twice
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_BreakScreens;
}
@ -3547,7 +3549,6 @@ void SetMoveEffect(enum BattlerId battlerAtk, enum BattlerId effectBattler, enum
if (gBattleStruct->stolenStats[0] != 0)
{
gBattleStruct->preAttackAnimPlayed = FALSE; // if set by the previous move effect, reset it for final Spectral Thief anim
BattleScriptPush(battleScript);
gBattlescriptCurrInstr = BattleScript_StealStats;
}
@ -3584,27 +3585,54 @@ static void Cmd_setpreattackadditionaleffect(void)
{
CMD_ARGS();
u32 numAdditionalEffects = GetMoveAdditionalEffectCount(gCurrentMove);
if (numAdditionalEffects > gBattleStruct->additionalEffectsCounter)
if (gBattleStruct->preAttackEffectHappened)
{
const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(gCurrentMove, gBattleStruct->additionalEffectsCounter);
gBattleStruct->additionalEffectsCounter++;
if (!additionalEffect->preAttackEffect)
return;
SetMoveEffect(
gBattlerAttacker,
additionalEffect->self ? gBattlerAttacker : gBattlerTarget,
additionalEffect->moveEffect,
gBattlescriptCurrInstr,
EFFECT_PRIMARY
);
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
while (gEffectBattler != MAX_BATTLERS_COUNT)
{
u32 numAdditionalEffects = GetMoveAdditionalEffectCount(gCurrentMove);
if (numAdditionalEffects > gBattleStruct->additionalEffectsCounter)
{
const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(gCurrentMove, gBattleStruct->additionalEffectsCounter);
gBattleStruct->additionalEffectsCounter++;
if (!additionalEffect->preAttackEffect)
return;
if ((gEffectBattler == gBattlerAttacker) != additionalEffect->self)
return;
u32 percentChance = CalcSecondaryEffectChance(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), additionalEffect);
// Activate effect if it's primary (chance == 0) or if RNGesus says so
if ((percentChance == 0) || RandomPercentage(RNG_SECONDARY_EFFECT + gBattleStruct->additionalEffectsCounter, percentChance))
{
gBattleCommunication[MULTISTRING_CHOOSER] = *((u8 *) &additionalEffect->multistring);
enum SetMoveEffectFlags flags = NO_FLAGS;
if (percentChance == 0) flags |= EFFECT_PRIMARY;
if (percentChance >= 100) flags |= EFFECT_CERTAIN;
SetMoveEffect(
gBattlerAttacker,
gEffectBattler,
additionalEffect->moveEffect,
gBattlescriptCurrInstr,
flags
);
}
return;
}
gEffectBattler = GetPossibleNextTarget(gEffectBattler);
gBattleStruct->additionalEffectsCounter = 0;
}
gEffectBattler = gBattlerAttacker;
gBattleStruct->additionalEffectsCounter = 0;
gBattleStruct->preAttackEffectHappened = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
@ -3681,13 +3709,11 @@ static void Cmd_setadditionaleffects(void)
gBattlescriptCurrInstr,
flags
);
gBattleStruct->additionalEffectsCounter++;
return;
}
}
gBattleStruct->additionalEffectsCounter++;
return;
}
}
@ -6263,7 +6289,6 @@ static void ResetValuesForCalledMove(void)
gBattleScripting.animTurn = 0;
gBattleScripting.animTargetsHit = 0;
SetTypeBeforeUsingMove(gCurrentMove, gBattlerAttacker);
DetermineTarget(GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove), FALSE);
ClearDamageCalcResults();
}
@ -6316,8 +6341,9 @@ static void Cmd_futuresighttargetfailure(void)
}
#undef DONE_TARGET_FAILURE
static u32 GetPossibleNextTarget(void)
static u32 GetPossibleNextTarget(u32 currTarget)
{
u32 i = 0;
const u8 targetOrder[MAX_BATTLERS_COUNT] = {
gBattlerAttacker,
BATTLE_PARTNER(gBattlerAttacker),
@ -6325,9 +6351,20 @@ static u32 GetPossibleNextTarget(void)
RIGHT_FOE(gBattlerAttacker),
};
for (u32 i = 0; i < MAX_BATTLERS_COUNT; i++)
// currTarget allows for a starting point without relying on values for previous targets being set
if (currTarget != MAX_BATTLERS_COUNT)
{
enum BattlerId battler = targetOrder[i];
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
{
if (targetOrder[i] == currTarget)
break;
}
i++; // next target after finding currTarget
}
while (i < MAX_BATTLERS_COUNT)
{
enum BattlerId battler = targetOrder[i++];
if (!IsBattlerAlive(battler))
continue;
@ -6344,7 +6381,7 @@ static void Cmd_getpossiblenexttarget(void)
{
CMD_ARGS(const u8 *jumpInstr);
u32 nextTarget = GetPossibleNextTarget();
u32 nextTarget = GetPossibleNextTarget(MAX_BATTLERS_COUNT);
if (nextTarget != MAX_BATTLERS_COUNT)
{
gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = nextTarget;
@ -6451,19 +6488,16 @@ static void Cmd_removeitem(void)
{
CMD_ARGS(u8 battler);
enum BattlerId battler;
enum Item itemId = 0;
enum BattlerId battler = GetBattlerForBattleScript(cmd->battler);
enum Item itemId = gBattleMons[battler].item;
if (gBattleScripting.overrideBerryRequirements)
if (gBattleScripting.overrideBerryRequirements || itemId == ITEM_NONE)
{
// bug bite / pluck - don't remove current item
// bug bite / pluck / no item - don't remove current item
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
battler = GetBattlerForBattleScript(cmd->battler);
itemId = gBattleMons[battler].item;
// Popped Air Balloon cannot be restored by any means.
// Corroded items cannot be restored either.
if (GetBattlerHoldEffect(battler) != HOLD_EFFECT_AIR_BALLOON
@ -12627,9 +12661,8 @@ void BS_SetMagicCoatTarget(void)
gBattleStruct->attackerBeforeBounce = gBattleScripting.battler = gBattlerAttacker;
gBattlerAttacker = gBattlerTarget;
gBattlerTarget = gBattleStruct->attackerBeforeBounce;
DetermineTarget(GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove), FALSE);
ClearDamageCalcResults();
gBattleStruct->eventState.atkCanceler = CANCELER_TARGET_FAILURE;
gBattleStruct->eventState.atkCanceler = CANCELER_SET_TARGETS;
gBattleStruct->eventState.atkCancelerBattler = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
@ -13381,7 +13414,6 @@ void BS_SetLastUsedItem(void)
{
NATIVE_ARGS(u8 battler);
gLastUsedItem = gBattleMons[GetBattlerForBattleScript(cmd->battler)].item;
gBattleStruct->flingItem = gLastUsedItem;
gBattlescriptCurrInstr = cmd->nextInstr;
}

View File

@ -368,175 +368,9 @@ static bool32 IsUnnerveAbilityOnOpposingSide(enum BattlerId battler)
return FALSE;
}
static inline bool32 IsDragonDartsSecondHit(enum BattlerId battlerAtk, enum Move move)
{
if (GetBattlerMoveTargetType(battlerAtk, move) != TARGET_SMART)
return FALSE;
if (gMultiHitCounter < GetMoveStrikeCount(move))
return TRUE;
return FALSE;
}
bool32 IsAffectedByFollowMe(enum BattlerId battlerAtk, enum BattleSide defSide, enum Move move)
{
enum Ability ability = GetBattlerAbility(battlerAtk);
enum BattleMoveEffects effect = GetMoveEffect(move);
if (gSideTimers[defSide].followmeTimer == 0
|| (!IsBattlerAlive(gSideTimers[defSide].followmeTarget) && !IsDragonDartsSecondHit(battlerAtk, move))
|| effect == EFFECT_SNIPE_SHOT
|| effect == EFFECT_SKY_DROP
|| IsAbilityAndRecord(battlerAtk, ability, ABILITY_PROPELLER_TAIL)
|| IsAbilityAndRecord(battlerAtk, ability, ABILITY_STALWART))
return FALSE;
if (effect == EFFECT_PURSUIT && IsPursuitTargetSet())
return FALSE;
if (gSideTimers[defSide].followmePowder && !IsAffectedByPowderMove(battlerAtk, ability, GetBattlerHoldEffect(battlerAtk)))
return FALSE;
return TRUE;
}
static bool32 HandleMoveTargetRedirection(void)
{
u32 redirectorOrderNum = MAX_BATTLERS_COUNT;
enum MoveTarget moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
enum BattleMoveEffects moveEffect = GetMoveEffect(gCurrentMove);
enum BattleSide side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker));
if (moveEffect == EFFECT_REFLECT_DAMAGE)
{
enum DamageCategory reflectCategory = GetReflectDamageMoveDamageCategory(gBattlerAttacker, gCurrentMove);
if (reflectCategory == DAMAGE_CATEGORY_PHYSICAL)
gBattleStruct->moveTarget[gBattlerAttacker] = gProtectStructs[gBattlerAttacker].physicalBattlerId;
else
gBattleStruct->moveTarget[gBattlerAttacker] = gProtectStructs[gBattlerAttacker].specialBattlerId;
}
if (IsAffectedByFollowMe(gBattlerAttacker, side, gCurrentMove)
&& (moveTarget == TARGET_SELECTED || moveTarget == TARGET_SMART || moveEffect == EFFECT_REFLECT_DAMAGE)
&& !IsBattlerAlly(gBattlerAttacker, gSideTimers[side].followmeTarget))
{
gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = gSideTimers[side].followmeTarget; // follow me moxie fix
return TRUE;
}
enum Type moveType = GetBattleMoveType(gCurrentMove);
enum Ability ability = GetBattlerAbility(gBattleStruct->moveTarget[gBattlerAttacker]);
bool32 currTargetCantAbsorb = ((ability != ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC)
|| (ability != ABILITY_STORM_DRAIN && moveType == TYPE_WATER));
if (currTargetCantAbsorb
&& IsDoubleBattle()
&& gSideTimers[side].followmeTimer == 0
&& moveTarget != TARGET_USER
&& moveTarget != TARGET_ALL_BATTLERS
&& moveTarget != TARGET_FIELD)
{
// Find first battler that redirects the move (in turn order)
enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker);
enum BattlerId battler;
for (battler = 0; battler < gBattlersCount; battler++)
{
ability = GetBattlerAbility(battler);
if ((B_REDIRECT_ABILITY_ALLIES >= GEN_4 || !IsBattlerAlly(gBattlerAttacker, battler))
&& battler != gBattlerAttacker
&& gBattleStruct->moveTarget[gBattlerAttacker] != battler
&& ((ability == ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC)
|| (ability == ABILITY_STORM_DRAIN && moveType == TYPE_WATER))
&& GetBattlerTurnOrderNum(battler) < redirectorOrderNum
&& moveEffect != EFFECT_SNIPE_SHOT
&& moveEffect != EFFECT_PLEDGE
&& !IsAbilityAndRecord(gBattlerAttacker, abilityAtk, ABILITY_PROPELLER_TAIL)
&& !IsAbilityAndRecord(gBattlerAttacker, abilityAtk, ABILITY_STALWART))
{
redirectorOrderNum = GetBattlerTurnOrderNum(battler);
}
}
if (redirectorOrderNum != MAX_BATTLERS_COUNT && moveEffect != EFFECT_TEATIME)
{
enum Ability battlerAbility;
battler = gBattlerByTurnOrder[redirectorOrderNum];
battlerAbility = GetBattlerAbility(battler);
RecordAbilityBattle(battler, battlerAbility);
gSpecialStatuses[battler].abilityRedirected = TRUE;
gBattlerTarget = battler;
return TRUE;
}
}
return FALSE;
}
void DetermineTarget(enum MoveTarget moveTarget, bool32 overwriteTarget)
{
if (HandleMoveTargetRedirection())
return;
if (IsDoubleBattle() && moveTarget == TARGET_RANDOM)
{
gBattlerTarget = SetRandomTarget(gBattlerAttacker);
if (gAbsentBattlerFlags & (1u << gBattlerTarget)
&& !IsBattlerAlly(gBattlerAttacker, gBattlerTarget))
{
gBattlerTarget = GetPartnerBattler(gBattlerTarget);
}
}
else if (moveTarget == TARGET_ALLY)
{
if (IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker)) && !gProtectStructs[BATTLE_PARTNER(gBattlerAttacker)].usedAllySwitch)
gBattlerTarget = BATTLE_PARTNER(gBattlerAttacker);
else
gBattlerTarget = gBattlerAttacker;
}
else if (IsDoubleBattle() && moveTarget == TARGET_FOES_AND_ALLY)
{
for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++)
{
if (gBattlerTarget == gBattlerAttacker)
continue;
if (IsBattlerAlive(gBattlerTarget))
break;
}
}
else if (moveTarget == TARGET_USER || moveTarget == TARGET_USER_AND_ALLY)
{
gBattlerTarget = gBattlerAttacker;
}
else if (overwriteTarget)
{
gBattlerTarget = gBattleStruct->moveTarget[gBattlerAttacker];
if (!IsBattlerAlive(gBattlerTarget)
&& moveTarget != TARGET_OPPONENTS_FIELD
&& IsDoubleBattle()
&& (!IsBattlerAlly(gBattlerAttacker, gBattlerTarget)))
{
gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget)));
}
}
}
static bool32 WasOriginalTargetAlly(enum MoveTarget target)
{
if (!gProtectStructs[BATTLE_PARTNER(gBattlerAttacker)].usedAllySwitch)
return FALSE;
if ((target == TARGET_ALLY || target == TARGET_USER_OR_ALLY) && gBattlerAttacker == gBattlerTarget)
return TRUE;
return FALSE;
}
// Functions
void HandleAction_UseMove(void)
{
u32 i;
enum MoveTarget moveTarget;
gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber];
if (gAbsentBattlerFlags & 1u << gBattlerAttacker
|| gBattleStruct->battlerState[gBattlerAttacker].commandingDondozo
@ -617,10 +451,10 @@ void HandleAction_UseMove(void)
ClearDamageCalcResults();
gMultiHitCounter = 0;
gBattleCommunication[MISS_TYPE] = 0;
moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
DetermineTarget(moveTarget, TRUE);
gBattlerTarget = gBattleStruct->moveTarget[gBattlerAttacker];
// Avoid assert errors before failure script is played (for ally targeting moves)
if (!IsDoubleBattle() && gBattlerTarget >= gBattlersCount)
gBattlerTarget = BATTLE_PARTNER(gBattlerTarget);
if (gBattleTypeFlags & BATTLE_TYPE_PALACE && gProtectStructs[gBattlerAttacker].palaceUnableToUseMove)
{
@ -642,14 +476,6 @@ void HandleAction_UseMove(void)
gBattlescriptCurrInstr = BattleScript_MoveUsedLoafingAround;
}
}
else if (IsBattlerAlly(gBattlerAttacker, gBattlerTarget) && !IsBattlerAlive(gBattlerTarget))
{
gBattlescriptCurrInstr = BattleScript_FailedFromAtkCanceler;
}
else if (WasOriginalTargetAlly(moveTarget))
{
gBattlescriptCurrInstr = BattleScript_FailedFromAtkCanceler;
}
else
{
gBattlescriptCurrInstr = GetMoveBattleScript(gCurrentMove);
@ -658,10 +484,10 @@ void HandleAction_UseMove(void)
if (gBattleTypeFlags & BATTLE_TYPE_ARENA)
BattleArena_AddMindPoints(gBattlerAttacker);
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
for (enum BattlerId battler = 0; battler < gBattlersCount; battler++)
{
gBattleStruct->battlerState[i].wasAboveHalfHp = gBattleMons[i].hp > gBattleMons[i].maxHP / 2;
gBattleMons[i].volatiles.activateDancer = FALSE;
gBattleStruct->battlerState[battler].wasAboveHalfHp = gBattleMons[battler].hp > gBattleMons[battler].maxHP / 2;
gBattleMons[battler].volatiles.activateDancer = FALSE;
}
gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT;
@ -5760,7 +5586,6 @@ u32 GetBattleMoveTarget(enum Move move, enum MoveTarget moveTarget)
{
u32 targetBattler = 0;
enum BattleSide side;
enum Type moveType = GetBattleMoveType(move);
if (moveTarget == TARGET_NONE)
moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, move);
@ -5770,45 +5595,12 @@ u32 GetBattleMoveTarget(enum Move move, enum MoveTarget moveTarget)
case TARGET_SELECTED:
case TARGET_SMART:
case TARGET_OPPONENT:
case TARGET_RANDOM:
side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker));
if (IsAffectedByFollowMe(gBattlerAttacker, side, move))
{
targetBattler = gSideTimers[side].followmeTarget;
}
else
{
enum Ability battlerAbilityOnField = 0;
targetBattler = SetRandomTarget(gBattlerAttacker);
if (moveType == TYPE_ELECTRIC && GetBattlerAbility(targetBattler) != ABILITY_LIGHTNING_ROD)
{
if (B_REDIRECT_ABILITY_ALLIES >= GEN_4)
battlerAbilityOnField = IsAbilityOnField(ABILITY_LIGHTNING_ROD);
else
battlerAbilityOnField = IsAbilityOnOpposingSide(targetBattler, ABILITY_LIGHTNING_ROD);
if (battlerAbilityOnField > 0 && (battlerAbilityOnField - 1) != gBattlerAttacker)
{
targetBattler = battlerAbilityOnField - 1;
RecordAbilityBattle(targetBattler, gBattleMons[targetBattler].ability);
gSpecialStatuses[targetBattler].abilityRedirected = TRUE;
}
}
else if (moveType == TYPE_WATER && GetBattlerAbility(targetBattler) != ABILITY_STORM_DRAIN)
{
if (B_REDIRECT_ABILITY_ALLIES >= GEN_4)
battlerAbilityOnField = IsAbilityOnField(ABILITY_STORM_DRAIN);
else
battlerAbilityOnField = IsAbilityOnOpposingSide(targetBattler, ABILITY_STORM_DRAIN);
if (battlerAbilityOnField > 0 && (battlerAbilityOnField - 1) != gBattlerAttacker)
{
targetBattler = battlerAbilityOnField - 1;
RecordAbilityBattle(targetBattler, gBattleMons[targetBattler].ability);
gSpecialStatuses[targetBattler].abilityRedirected = TRUE;
}
}
}
break;
case TARGET_DEPENDS:
case TARGET_BOTH:
@ -5820,15 +5612,6 @@ u32 GetBattleMoveTarget(enum Move move, enum MoveTarget moveTarget)
case TARGET_OPPONENTS_FIELD:
targetBattler = GetOpposingSideBattler(gBattlerAttacker);
break;
case TARGET_RANDOM:
side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker));
if (IsAffectedByFollowMe(gBattlerAttacker, side, move))
targetBattler = gSideTimers[side].followmeTarget;
else if (IsDoubleBattle())
targetBattler = SetRandomTarget(gBattlerAttacker);
else
targetBattler = GetOpposingSideBattler(gBattlerAttacker);
break;
case TARGET_USER:
default:
targetBattler = gBattlerAttacker;
@ -10077,7 +9860,8 @@ void ClearDamageCalcResults(void)
gBattleStruct->printedStrongWindsWeakenedAttack = FALSE;
gBattleStruct->numSpreadTargets = 0;
gBattleStruct->unableToUseMove = FALSE;
gBattleStruct->preAttackAnimPlayed = FALSE;
gBattleStruct->attackAnimPlayed = FALSE;
gBattleStruct->preAttackEffectHappened = FALSE;
gBattleScripting.savedDmg = 0;
if (gCurrentMove != MOVE_NONE)
gBattleStruct->moldBreakerActive = IsMoldBreakerTypeAbility(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) || MoveIgnoresTargetAbility(gCurrentMove);

View File

@ -6809,6 +6809,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
.additionalEffects = ADDITIONAL_EFFECTS({
.moveEffect = MOVE_EFFECT_BEAT_UP_MESSAGE,
.preAttackEffect = TRUE,
.self = TRUE,
}),
.contestEffect = C_UPDATED_MOVE_EFFECTS >= GEN_6 ? CONTEST_EFFECT_BETTER_WITH_GOOD_CONDITION : CONTEST_EFFECT_BADLY_STARTLE_MONS_WITH_GOOD_APPEALS,
.contestCategory = CONTEST_CATEGORY_SMART,

View File

@ -122,8 +122,7 @@ SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail prevent Protean act
}
}
// Listed on Bulbapedia as "Moves that target all Pokémon (except Perish Song, Flower Shield, and Rototiller),"
// Despite the fact that there's only 2 remaining moves from that list, being Haze and Teatime
// Moves that target the field don't get blocked
SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail do not block Haze")
{
u32 species;
@ -135,6 +134,7 @@ SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail do not block Haze")
GIVEN {
ASSUME(GetMoveEffect(MOVE_HAZE) == EFFECT_HAZE);
ASSUME(GetMoveTarget(MOVE_HAZE) == TARGET_FIELD);
PLAYER(SPECIES_MURKROW) { Ability(ABILITY_PRANKSTER); }
OPPONENT(species) { Ability(ability); }
} WHEN {
@ -143,14 +143,15 @@ SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail do not block Haze")
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_HAZE, player);
NOT ABILITY_POPUP(opponent, ability);
ANIMATION(ANIM_TYPE_MOVE, MOVE_HAZE, player);
} THEN {
EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE);
}
}
SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail do not block Teatime")
// Moves that target all battlers are blocked
SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail on opponents block Teatime")
{
u32 species;
enum Ability ability;
@ -161,17 +162,18 @@ SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail do not block Teatim
GIVEN {
ASSUME(GetMoveEffect(MOVE_TEATIME) == EFFECT_TEATIME);
ASSUME(GetMoveTarget(MOVE_TEATIME) == TARGET_ALL_BATTLERS);
ASSUME(GetItemHoldEffect(ITEM_ORAN_BERRY) == HOLD_EFFECT_RESTORE_HP);
PLAYER(SPECIES_MURKROW) { Ability(ABILITY_PRANKSTER); Item(ITEM_ORAN_BERRY); HP(75); MaxHP(100); }
OPPONENT(species) { Ability(ability); Item(ITEM_ORAN_BERRY); HP(75); MaxHP(100); }
} WHEN {
TURN { MOVE(player, MOVE_TEATIME); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, player);
NOT ABILITY_POPUP(opponent, ability);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, player);
ABILITY_POPUP(opponent, ability);
} THEN {
EXPECT_EQ(player->item, ITEM_NONE);
EXPECT_EQ(opponent->item, ITEM_NONE);
EXPECT_NE(player->item, ITEM_NONE);
EXPECT_NE(opponent->item, ITEM_NONE);
}
}

View File

@ -74,10 +74,10 @@ DOUBLE_BATTLE_TEST("Magic Coat reflects hazards regardless of the user's positio
struct BattlePokemon *coatUser = NULL;
PARAMETRIZE { coatUser = playerLeft; }
PARAMETRIZE { coatUser = playerRight; }
ASSUME(GetMoveEffect(MOVE_SPIKES) == EFFECT_SPIKES);
ASSUME(GetMoveEffect(MOVE_STEALTH_ROCK) == EFFECT_STEALTH_ROCK);
GIVEN {
ASSUME(GetMoveEffect(MOVE_SPIKES) == EFFECT_SPIKES);
ASSUME(GetMoveEffect(MOVE_STEALTH_ROCK) == EFFECT_STEALTH_ROCK);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
@ -97,3 +97,33 @@ DOUBLE_BATTLE_TEST("Magic Coat reflects hazards regardless of the user's positio
EXPECT(IsHazardOnSide(B_SIDE_OPPONENT, HAZARDS_SPIKES));
}
}
SINGLE_BATTLE_TEST("Magic Coat reflection doesn't activate Protean/Libero")
{
u32 species, ability;
PARAMETRIZE { species = SPECIES_GRENINJA; ability = ABILITY_PROTEAN; }
PARAMETRIZE { species = SPECIES_CINDERACE; ability = ABILITY_LIBERO; }
GIVEN {
ASSUME(GetMoveEffect(MOVE_SPIKES) == EFFECT_SPIKES);
ASSUME(GetMoveType(MOVE_MAGIC_COAT) == TYPE_PSYCHIC);
ASSUME(GetMoveType(MOVE_SPIKES) != TYPE_PSYCHIC);
PLAYER(species) { Ability(ability); }
OPPONENT(SPECIES_ZIGZAGOON);
} WHEN {
TURN { MOVE(player, MOVE_MAGIC_COAT); MOVE(opponent, MOVE_SPIKES); }
} SCENE {
ABILITY_POPUP(player, ability);
ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_COAT, player);
ONE_OF {
MESSAGE("Greninja bounced the Spikes back!");
MESSAGE("Cinderace bounced the Spikes back!");
}
NOT ABILITY_POPUP(player, ability);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, player);
} THEN {
EXPECT_EQ(player->types[0], TYPE_PSYCHIC);
EXPECT_EQ(player->types[1], TYPE_PSYCHIC);
}
}