diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index c9b9675afc..9ca8a0c80c 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -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:: diff --git a/include/battle.h b/include/battle.h index a6eb30ac85..e2c8ba1415 100644 --- a/include/battle.h +++ b/include/battle.h @@ -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; diff --git a/include/battle_util.h b/include/battle_util.h index 77a52fb641..a13d59c22b 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -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); diff --git a/include/constants/battle_move_resolution.h b/include/constants/battle_move_resolution.h index e3a073b19d..8d2985df66 100644 --- a/include/constants/battle_move_resolution.h +++ b/include/constants/battle_move_resolution.h @@ -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, diff --git a/src/battle_move_resolution.c b/src/battle_move_resolution.c index cd3eb24c46..b5bfa511bd 100644 --- a/src/battle_move_resolution.c +++ b/src/battle_move_resolution.c @@ -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) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 11b7ab65c9..d2e1c7ae4e 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -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; } diff --git a/src/battle_util.c b/src/battle_util.c index 8d98149fe7..1d75c80481 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -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); diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 402b6d8460..d2ab439fbd 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -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, diff --git a/test/battle/ability/dazzling.c b/test/battle/ability/dazzling.c index 2a55e61618..ca2471166f 100644 --- a/test/battle/ability/dazzling.c +++ b/test/battle/ability/dazzling.c @@ -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); } } diff --git a/test/battle/move_effect/magic_coat.c b/test/battle/move_effect/magic_coat.c index 05e53b5e12..e17dbe7347 100644 --- a/test/battle/move_effect/magic_coat.c +++ b/test/battle/move_effect/magic_coat.c @@ -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); + } +}