From c114dfbc84649eb623d7ad65ea082f6f73db9e51 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:12:28 +0100 Subject: [PATCH] Refactor Sky Drop and rampage confusion (#9249) --- asm/macros/battle_script.inc | 9 +- data/battle_scripts_1.s | 20 +-- include/battle.h | 1 - include/battle_main.h | 2 +- include/battle_scripts.h | 2 +- include/battle_util.h | 18 +-- include/config/battle.h | 2 +- include/constants/battle.h | 5 +- include/constants/battle_move_resolution.h | 3 +- include/constants/battle_script_commands.h | 2 +- include/constants/generational_changes.h | 2 +- src/battle_ai_items.c | 2 +- src/battle_ai_util.c | 4 +- src/battle_anim_effects_1.c | 19 --- src/battle_end_turn.c | 12 +- src/battle_main.c | 51 +------ src/battle_move_resolution.c | 159 +++++++++++---------- src/battle_script_commands.c | 133 +++++++---------- src/battle_util.c | 156 +++++++------------- test/battle/move_effect/sky_drop.c | 83 +++++++++++ test/battle/move_effect_secondary/thrash.c | 25 +++- 21 files changed, 337 insertions(+), 373 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 80946c4f0b..f40dfcf9bd 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1204,6 +1204,11 @@ .byte B_SCR_OP_TRY_SYNCHRONIZE .endm + .macro tryconfusionafterskydrop battler:req + .byte B_SCR_OP_TRY_CONFUSION_AFTER_SKY_DROP + .byte \battler + .endm + .macro callnative func:req .byte B_SCR_OP_CALLNATIVE .4byte \func @@ -2082,10 +2087,6 @@ .4byte \failInstr .endm - .macro skydropyawn - callnative BS_SkyDropYawn - .endm - .macro jumpifpranksterblocked jumpInstr:req callnative BS_JumpIfPranksterBlocked .4byte \jumpInstr diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 47aa4007fc..21ce5144eb 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -2118,6 +2118,8 @@ BattleScript_EffectGravitySuccess:: selectfirstvalidtarget BattleScript_GravityLoop: jumpfifsemiinvulnerable BS_TARGET, STATE_ON_AIR, BattleScript_GravityLoopDrop + jumpfifsemiinvulnerable BS_TARGET, STATE_SKY_DROP_ATTACKER, BattleScript_GravityLoopDrop + jumpfifsemiinvulnerable BS_TARGET, STATE_SKY_DROP_TARGET, BattleScript_GravityLoopDrop jumpifvolatile BS_TARGET, VOLATILE_MAGNET_RISE, BattleScript_GravityLoopDrop jumpifvolatile BS_TARGET, VOLATILE_TELEKINESIS, BattleScript_GravityLoopDrop goto BattleScript_GravityLoopEnd @@ -2832,18 +2834,6 @@ BattleScript_SkyDropFlyingType:: printstring STRINGID_ITDOESNTAFFECT waitmessage B_WAIT_TIME_LONG makevisible BS_ATTACKER - jumpifvolatile BS_TARGET, VOLATILE_CONFUSION, BattleScript_SkyDropFlyingAlreadyConfused - jumpifvolatile BS_TARGET, VOLATILE_RAMPAGE_TURNS, BattleScript_SkyDropFlyingConfuseLock - return - -BattleScript_SkyDropFlyingConfuseLock: - seteffectprimary BS_ATTACKER, BS_TARGET, MOVE_EFFECT_CONFUSION -BattleScript_SkyDropFlyingAlreadyConfused: - clearvolatile BS_TARGET, VOLATILE_RAMPAGE_TURNS - jumpifvolatile BS_TARGET, VOLATILE_CONFUSION, BattleScript_SkyDropFlyingAlreadyConfusedRet - setbyte BS_ATTACKER, BS_TARGET - call BattleScript_ThrashConfusesRet -BattleScript_SkyDropFlyingAlreadyConfusedRet: return BattleScript_SkyDropNoTarget:: @@ -3978,6 +3968,7 @@ BattleScript_FaintBattler:: dofaintanimation BS_FAINTED copybyte sBATTLER, gBattlerFainted @ for message printstring STRINGID_BATTLERFAINTED + tryconfusionafterskydrop BS_FAINTED cleareffectsonfaint BS_FAINTED trytoclearprimalweather tryrevertweatherform @@ -5705,7 +5696,7 @@ BattleScript_ThrashConfuses:: waitmessage B_WAIT_TIME_LONG end2 -BattleScript_ThrashConfusesRet:: +BattleScript_ConfusionAfterRampage:: volatileanimation BS_SCRIPTING, VOLATILE_CONFUSION printstring STRINGID_PKMNFATIGUECONFUSION waitmessage B_WAIT_TIME_LONG @@ -5837,9 +5828,8 @@ BattleScript_YawnMakesAsleepEnd2:: updatestatusicon BS_EFFECT_BATTLER waitstate tryactivateitem BS_EFFECT_BATTLER, ACTIVATION_ON_STATUS_CHANGE - jumpfifsemiinvulnerable BS_EFFECT_BATTLER, STATE_SKY_DROP, BattleScript_YawnEnd + jumpfifsemiinvulnerable BS_EFFECT_BATTLER, STATE_SKY_DROP_TARGET, BattleScript_YawnEnd makevisible BS_EFFECT_BATTLER - skydropyawn BattleScript_YawnEnd: end2 diff --git a/include/battle.h b/include/battle.h index 3cf9a41817..1cb6d5dba0 100644 --- a/include/battle.h +++ b/include/battle.h @@ -653,7 +653,6 @@ struct BattleStruct u8 throwingPokeBall:1; u8 ballSpriteIds[2]; // item gfx, window gfx u8 moveInfoSpriteId; // move info, window gfx - u8 skyDropTargets[MAX_BATTLERS_COUNT]; // For Sky Drop, to account for if multiple Pokemon use Sky Drop in a double battle. // When using a move which hits multiple opponents which is then bounced by a target, we need to make sure, the move hits both opponents, the one with bounce, and the one without. u16 beatUpSpecies[PARTY_SIZE]; // Species for Gen5+ Beat Up, otherwise party indexes u8 attackerBeforeBounce:2; diff --git a/include/battle_main.h b/include/battle_main.h index a63e653392..f288a018e6 100644 --- a/include/battle_main.h +++ b/include/battle_main.h @@ -90,7 +90,7 @@ void AnimSetCenterToCornerVecX(struct Sprite *sprite); void BeginBattleIntroDummy(void); void BeginBattleIntro(void); void SwitchInClearSetData(enum BattlerId battler, struct Volatiles *volatilesCopy); -const u8 *FaintClearSetData(enum BattlerId battler); +void FaintClearSetData(enum BattlerId battler); void BattleTurnPassed(void); u8 IsRunningFromBattleImpossible(enum BattlerId battler); void SwitchTwoBattlersInParty(enum BattlerId battler, enum BattlerId battler2); diff --git a/include/battle_scripts.h b/include/battle_scripts.h index ada1e6bee4..ff932f818c 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -133,7 +133,7 @@ extern const u8 BattleScript_MoveUsedIsParalyzed[]; extern const u8 BattleScript_MoveUsedFlinched[]; extern const u8 BattleScript_PrintUproarOverTurns[]; extern const u8 BattleScript_ThrashConfuses[]; -extern const u8 BattleScript_ThrashConfusesRet[]; +extern const u8 BattleScript_ConfusionAfterRampage[]; extern const u8 BattleScript_MoveUsedIsConfused[]; extern const u8 BattleScript_MoveUsedIsConfusedNoMore[]; extern const u8 BattleScript_PrintPayDayMoneyString[]; diff --git a/include/battle_util.h b/include/battle_util.h index 95fb5a3823..0260593b12 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -152,19 +152,6 @@ enum SleepClauseBlock BLOCKED_BY_SLEEP_CLAUSE, }; -enum SkyDropState -{ - SKY_DROP_IGNORE, - SKY_DROP_ATTACKCANCELER_CHECK, - SKY_DROP_GRAVITY_ON_AIRBORNE, - SKY_DROP_CANCEL_MULTI_TURN_MOVES, - SKY_DROP_STATUS_YAWN, - SKY_DROP_STATUS_FREEZE_SLEEP, -}; - -#define SKY_DROP_NO_TARGET 0xFF -#define SKY_DROP_RELEASED_TARGET 0xFE - enum EjectPackTiming { START_OF_TURN, @@ -197,7 +184,7 @@ enum BattlerId GetBattlerForBattleScript(u8 caseId); bool32 IsBattlerMarkedForControllerExec(enum BattlerId battler); void MarkBattlerForControllerExec(enum BattlerId battler); void MarkBattlerReceivedLinkData(enum BattlerId battler); -const u8 *CancelMultiTurnMoves(enum BattlerId battler, enum SkyDropState skyDropState); +void CancelMultiTurnMoves(enum BattlerId battler); bool32 IsLastMonToMove(enum BattlerId battler); bool32 ShouldDefiantCompetitiveActivate(enum BattlerId battler, enum Ability ability); void PrepareStringBattle(enum StringID stringId, enum BattlerId battler); @@ -349,7 +336,7 @@ bool32 CanBeParalyzed(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum bool32 CanBeFrozen(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Ability abilityDef); bool32 CanGetFrostbite(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Ability abilityDef); bool32 CanSetNonVolatileStatus(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Ability abilityAtk, enum Ability abilityDef, enum MoveEffect secondaryMoveEffect, enum ResultOption option); -bool32 CanBeConfused(enum BattlerId battler); +bool32 CanBeConfused(enum BattlerId battlerAtk, enum BattlerId effectBattler); bool32 IsSafeguardProtected(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Ability abilityAtk); u32 GetBattlerAffectionHearts(enum BattlerId battler); void TryToRevertMimicryAndFlags(void); @@ -405,6 +392,7 @@ bool32 DoesMoveMissTarget(struct BattleCalcValues *cv); bool32 IsSemiInvulnerable(enum BattlerId battler, enum SemiInvulnerableExclusion excludeCommander); bool32 CanBreakThroughSemiInvulnerablity(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Ability abilityAtk, enum Ability abilityDef, enum Move move); bool32 BreaksThroughSemiInvulnerableState(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Ability abilityAtk, enum Ability abilityDef, enum Move move, enum SemiInvulnerableState state); +bool32 IsBattlerOnAir(enum BattlerId battler); bool32 HasPartnerTrainer(enum BattlerId battler); bool32 IsAffectedByPowderMove(enum BattlerId battler, enum Ability ability, enum HoldEffect holdEffect); enum Move GetNaturePowerMove(void); diff --git a/include/config/battle.h b/include/config/battle.h index 9ed3fb6391..64129755f1 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -106,7 +106,7 @@ #define B_SKILL_SWAP GEN_LATEST // In Gen4+, Skill Swap triggers switch-in abilities after use. #define B_BRICK_BREAK GEN_LATEST // In Gen4+, you can destroy your own side's screens. In Gen 5+, screens are not removed if the target is immune. #define B_WISH_HP_SOURCE GEN_LATEST // In Gen5+, Wish heals half of the user's max HP instead of the target's. -#define B_RAMPAGE_CANCELLING GEN_LATEST // In Gen5+, a failed Thrash, etc, will cancel except on its last turn. +#define B_RAMPAGE_CONFUSION GEN_LATEST // In Gen5+, Rampage is canceled after the move (as opposed to End Turn) and a failed rampage move will cancel the counter unless it is the last turn #define B_HEAL_BLOCKING GEN_LATEST // In Gen5+, Heal Block prevents healing by Black Sludge, Leftovers, Shell Bell. Affected Pokémon will not consume held HP-restoring Berries or Berry Juice. // Draining abilities will not heal but will prevent damage. In Gen6+, Heal Block prevents the use of most HP-draining moves. #define B_ROOTED_GROUNDING GEN_LATEST // In Gen4+, Ingrain causes the affected Pokémon to become grounded. diff --git a/include/constants/battle.h b/include/constants/battle.h index e580cbfb61..eb9c8603b0 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -183,6 +183,8 @@ enum VolatileFlags F(VOLATILE_BIDE, bideTurns, (u32, 3)) \ F(VOLATILE_RAMPAGE_TURNS, rampageTurns, (u32, B_RAMPAGE_TURNS + 1)) \ F(VOLATILE_MULTIPLETURNS, multipleTurns, (u32, 1)) \ + F(VOLATILE_SKY_DROP_TARGET, skyDropTarget, (enum BattlerId, MAX_BATTLERS_COUNT)) \ + F(VOLATILE_CONFUSE_AFTER_DROP, confuseAfterDrop, (u32, 1)) \ F(VOLATILE_WRAPPED, wrapped, (u32, 1)) \ F(VOLATILE_WRAPPED_BY, wrappedBy, (enum BattlerId, MAX_BITS(MAX_BATTLERS_COUNT))) \ F(VOLATILE_WRAPPED_MOVE, wrappedMove, (u32, MOVES_COUNT_ALL - 1)) \ @@ -318,7 +320,8 @@ enum SemiInvulnerableState STATE_UNDERWATER, STATE_ON_AIR, STATE_PHANTOM_FORCE, - STATE_SKY_DROP, + STATE_SKY_DROP_ATTACKER, + STATE_SKY_DROP_TARGET, STATE_COMMANDER, SEMI_INVULNERABLE_COUNT }; diff --git a/include/constants/battle_move_resolution.h b/include/constants/battle_move_resolution.h index 0421644ba2..d656cba713 100644 --- a/include/constants/battle_move_resolution.h +++ b/include/constants/battle_move_resolution.h @@ -96,7 +96,6 @@ enum MoveEndState MOVEEND_SYMBIOSIS, MOVEEND_SUBSTITUTE, MOVEEND_FAINT_BLOCK, - MOVEEND_SKY_DROP_CONFUSE, MOVEEND_UPDATE_LAST_MOVES, MOVEEND_MIRROR_MOVE, MOVEEND_NEXT_TARGET, // Everything up until here is handled for each strike of a spread move @@ -121,6 +120,8 @@ enum MoveEndState MOVEEND_OPPORTUNIST, MOVEEND_MIRROR_HERB, MOVEEND_THIRD_MOVE_BLOCK, + MOVEEND_RAMPAGE, + MOVEEND_CONFUSION_AFTER_SKY_DROP, // If target was previously rampaging, it should be confused when dropped MOVEEND_EJECT_PACK, MOVEEND_CLEAR_BITS, MOVEEND_DANCER, diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 587d26572a..92f95ff1c4 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -226,6 +226,7 @@ enum BattleScriptOpcode B_SCR_OP_SETNONVOLATILESTATUS, B_SCR_OP_TRYOVERWRITEABILITY, B_SCR_OP_TRY_SYNCHRONIZE, + B_SCR_OP_TRY_CONFUSION_AFTER_SKY_DROP, // Expansion users, please don't use any of the unused commands. // They are reserved for expansion usage. @@ -261,7 +262,6 @@ enum BattleScriptOpcode B_SCR_OP_UNUSED_29, B_SCR_OP_UNUSED_30, B_SCR_OP_UNUSED_31, - B_SCR_OP_UNUSED_32, B_SCR_OP_CALLNATIVE, }; diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 5b6d7cb76f..77657f6a05 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -98,7 +98,7 @@ F(B_SKILL_SWAP, skillSwap, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(B_BRICK_BREAK, brickBreak, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(B_WISH_HP_SOURCE, wishHpSource, (u32, GEN_COUNT - 1)) \ - F(B_RAMPAGE_CANCELLING, rampageCancelling, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ + F(B_RAMPAGE_CONFUSION, rampageConfusion, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(B_HEAL_BLOCKING, healBlocking, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(B_ROOTED_GROUNDING, rootedGrounding, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(B_METRONOME_MOVES, metronomeMoves, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ diff --git a/src/battle_ai_items.c b/src/battle_ai_items.c index 66900b5e79..32e2a4143b 100644 --- a/src/battle_ai_items.c +++ b/src/battle_ai_items.c @@ -37,7 +37,7 @@ bool32 ShouldUseItem(enum BattlerId battler) // If teaming up with player and Pokemon is on the right, or Pokemon is currently held by Sky Drop if ((gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && GetBattlerPosition(battler) == B_POSITION_PLAYER_RIGHT) - || gBattleMons[battler].volatiles.semiInvulnerable == STATE_SKY_DROP) + || gBattleMons[battler].volatiles.semiInvulnerable == STATE_SKY_DROP_TARGET) return FALSE; if (gBattleMons[battler].volatiles.embargo) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 8b8bdb8985..f5226eafa4 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -427,7 +427,7 @@ bool32 IsBattlerTrapped(enum BattlerId battlerAtk, enum BattlerId battlerDef) return TRUE; if (gBattleMons[battlerDef].volatiles.escapePrevention) return TRUE; - if (gBattleMons[battlerDef].volatiles.semiInvulnerable == STATE_SKY_DROP) + if (gBattleMons[battlerDef].volatiles.semiInvulnerable == STATE_SKY_DROP_TARGET) return TRUE; if (gBattleMons[battlerDef].volatiles.root) return TRUE; @@ -3696,7 +3696,7 @@ bool32 AI_CanBeConfused(enum BattlerId battlerAtk, enum BattlerId battlerDef, en if (gBattleMons[battlerDef].volatiles.confusionTurns > 0 || (abilityDef == ABILITY_OWN_TEMPO && !DoesBattlerIgnoreAbilityChecks(battlerAtk, gAiLogicData->abilities[battlerAtk], move)) || IsMistyTerrainAffected(battlerDef, abilityDef, gAiLogicData->holdEffects[battlerDef], gFieldStatuses) - || gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SAFEGUARD + || IsSafeguardProtected(battlerAtk, battlerDef, gAiLogicData->abilities[battlerAtk]) || DoesSubstituteBlockMove(battlerAtk, battlerDef, move)) return FALSE; return TRUE; diff --git a/src/battle_anim_effects_1.c b/src/battle_anim_effects_1.c index c920689ba4..fd735d0fee 100644 --- a/src/battle_anim_effects_1.c +++ b/src/battle_anim_effects_1.c @@ -6583,24 +6583,6 @@ static void ReloadBattlerSprites(enum BattlerId battler, struct Pokemon *party) } } -static void TrySwapSkyDropTargets(enum BattlerId battlerAtk, enum BattlerId battlerPartner) -{ - u32 temp; - - // battlerAtk is using Ally Switch - // check if our partner is the target of sky drop - // If so, change that index to battlerAtk - for (enum BattlerId i = 0; i < gBattlersCount; i++) { - if (gBattleStruct->skyDropTargets[i] == battlerPartner) { - gBattleStruct->skyDropTargets[i] = battlerAtk; - break; - } - } - - // Then swap our own sky drop targets with the partner in case our partner is mid-skydrop - SWAP(gBattleStruct->skyDropTargets[battlerAtk], gBattleStruct->skyDropTargets[battlerPartner], temp); -} - #define TRY_SIDE_TIMER_BATTLER_ID_SWAP(battlerAtk, battlerPartner, side, field) \ if (gSideTimers[side].field == battlerAtk) \ gSideTimers[side].field = battlerPartner; \ @@ -6748,7 +6730,6 @@ static void AnimTask_AllySwitchDataSwap(u8 taskId) SwitchTwoBattlersInParty(battlerAtk, battlerPartner); SWAP(gBattlerPartyIndexes[battlerAtk], gBattlerPartyIndexes[battlerPartner], temp); - TrySwapSkyDropTargets(battlerAtk, battlerPartner); TrySwapStickyWebBattlerId(battlerAtk, battlerPartner); TrySwapWishBattlerIds(battlerAtk, battlerPartner); TrySwapAttractBattlerIds(battlerAtk, battlerPartner); diff --git a/src/battle_end_turn.c b/src/battle_end_turn.c index 1b0aec13cd..f2e5a30d8f 100644 --- a/src/battle_end_turn.c +++ b/src/battle_end_turn.c @@ -329,12 +329,14 @@ static bool32 HandleEndTurnFirstEventBlock(enum BattlerId battler) gBattleStruct->eventState.endTurnBlock++; break; case FIRST_EVENT_BLOCK_THRASH: - if (gBattleMons[battler].volatiles.rampageTurns && gBattleMons[battler].volatiles.semiInvulnerable != STATE_SKY_DROP) + if (B_RAMPAGE_CONFUSION < GEN_5 + && gBattleMons[battler].volatiles.rampageTurns + && gBattleMons[battler].volatiles.semiInvulnerable != STATE_SKY_DROP_TARGET) { gBattleMons[battler].volatiles.rampageTurns--; if (gBattleMons[battler].volatiles.unableToUseMove) { - CancelMultiTurnMoves(battler, SKY_DROP_IGNORE); + CancelMultiTurnMoves(battler); } else if (!gBattleMons[battler].volatiles.rampageTurns && gBattleMons[battler].volatiles.multipleTurns) { @@ -905,7 +907,7 @@ static bool32 HandleEndTurnYawn(enum BattlerId battler) else gBattleMons[battler].status1 |= (RandomUniform(RNG_SLEEP_TURNS, 2, 8)); - CancelMultiTurnMoves(battler, SKY_DROP_STATUS_YAWN); + CancelMultiTurnMoves(battler); TryActivateSleepClause(battler, gBattlerPartyIndexes[battler]); BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, 4, &gBattleMons[battler].status1); MarkBattlerForControllerExec(battler); @@ -1241,7 +1243,7 @@ static bool32 HandleEndTurnThirdEventBlock(enum BattlerId battler) gBattleMons[battler].volatiles.uproarTurns--; // uproar timer goes down if (gBattleMons[battler].volatiles.unableToUseMove) { - CancelMultiTurnMoves(battler, SKY_DROP_IGNORE); + CancelMultiTurnMoves(battler); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_UPROAR_ENDS; } else if (gBattleMons[battler].volatiles.uproarTurns) @@ -1252,7 +1254,7 @@ static bool32 HandleEndTurnThirdEventBlock(enum BattlerId battler) else { gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_UPROAR_ENDS; - CancelMultiTurnMoves(battler, SKY_DROP_IGNORE); + CancelMultiTurnMoves(battler); } BattleScriptExecute(BattleScript_PrintUproarOverTurns); effect = TRUE; diff --git a/src/battle_main.c b/src/battle_main.c index 164dc9d1c6..d5c5cff004 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3084,7 +3084,6 @@ static void BattleStartClearSetData(void) gBattleStruct->lastTakenMoveFrom[i][2] = MOVE_NONE; gBattleStruct->lastTakenMoveFrom[i][3] = MOVE_NONE; gBattleStruct->AI_monToSwitchIntoId[i] = PARTY_SIZE; - gBattleStruct->skyDropTargets[i] = SKY_DROP_NO_TARGET; } gLastUsedMove = 0; @@ -3328,10 +3327,8 @@ void SwitchInClearSetData(enum BattlerId battler, struct Volatiles *volatilesCop Ai_UpdateSwitchInData(battler); } -const u8* FaintClearSetData(enum BattlerId battler) +void FaintClearSetData(enum BattlerId battler) { - const u8 *result = NULL; - for (enum Stat i = 0; i < NUM_BATTLE_STATS; i++) gBattleMons[battler].statStages[i] = DEFAULT_STAT_STAGE; @@ -3427,48 +3424,6 @@ const u8* FaintClearSetData(enum BattlerId battler) Ai_UpdateFaintData(battler); TryBattleFormChange(battler, FORM_CHANGE_FAINT, GetBattlerAbility(battler)); - - // If the fainted mon was involved in a Sky Drop - if (gBattleStruct->skyDropTargets[battler] != SKY_DROP_NO_TARGET) - { - // Get battler id of the other Pokemon involved in this Sky Drop - u8 otherSkyDropper = gBattleStruct->skyDropTargets[battler]; - - // Clear Sky Drop data - gBattleStruct->skyDropTargets[battler] = SKY_DROP_NO_TARGET; - gBattleStruct->skyDropTargets[otherSkyDropper] = SKY_DROP_NO_TARGET; - - // If the other Pokemon involved in this Sky Drop was the target, not the attacker - if (gBattleMons[otherSkyDropper].volatiles.semiInvulnerable == STATE_SKY_DROP) - { - // Release the target and take them out of the semi-invulnerable state - gBattleMons[otherSkyDropper].volatiles.semiInvulnerable = STATE_NONE; - - // Make the target's sprite visible - gSprites[gBattlerSpriteIds[otherSkyDropper]].invisible = FALSE; - - // If the target was sky dropped in the middle of using Outrage/Petal Dance/Thrash, - // confuse them upon release and print "confused via fatigue" message and animation. - if (gBattleMons[otherSkyDropper].volatiles.rampageTurns) - { - gBattleMons[otherSkyDropper].volatiles.rampageTurns = 0; - - // If the released mon can be confused, do so. - // Don't use CanBeConfused here, since it can cause issues in edge cases. - enum Ability ability = GetBattlerAbility(otherSkyDropper); - if (!(ability == ABILITY_OWN_TEMPO - || gBattleMons[otherSkyDropper].volatiles.confusionTurns - || IsMistyTerrainAffected(otherSkyDropper, ability, GetBattlerHoldEffect(otherSkyDropper), gFieldStatuses))) - { - gBattleMons[otherSkyDropper].volatiles.confusionTurns = ((Random()) % 4) + 2; - gBattlerAttacker = otherSkyDropper; - result = BattleScript_ThrashConfuses; - } - } - } - } - - return result; } static void DoBattleIntro(void) @@ -3964,7 +3919,7 @@ static void HandleEndTurn_ContinueBattle(void) { gBattleMons[battler].volatiles.flinched = FALSE; if ((gBattleMons[battler].status1 & STATUS1_SLEEP) && (gBattleMons[battler].volatiles.multipleTurns)) - CancelMultiTurnMoves(battler, SKY_DROP_IGNORE); + CancelMultiTurnMoves(battler); } gBattleStruct->eventState.endTurnBlock = 0; gBattleStruct->eventState.endTurnBattler = 0; @@ -4298,7 +4253,7 @@ static void HandleTurnActionSelectionState(void) | BATTLE_TYPE_RECORDED_LINK)) && !gTestRunnerEnabled) // Or if currently held by Sky Drop - || gBattleMons[battler].volatiles.semiInvulnerable == STATE_SKY_DROP) + || gBattleMons[battler].volatiles.semiInvulnerable == STATE_SKY_DROP_TARGET) { RecordedBattle_ClearBattlerAction(battler, 1); gSelectionBattleScripts[battler] = BattleScript_ActionSelectionItemsCantBeUsed; diff --git a/src/battle_move_resolution.c b/src/battle_move_resolution.c index b96b46b23c..7f29bbf381 100644 --- a/src/battle_move_resolution.c +++ b/src/battle_move_resolution.c @@ -68,7 +68,7 @@ static enum CancelerResult CancelerStanceChangeOne(struct BattleContext *ctx) static enum CancelerResult CancelerSkyDrop(struct BattleContext *ctx) { // If Pokemon is being held in Sky Drop - if (gBattleMons[ctx->battlerAtk].volatiles.semiInvulnerable == STATE_SKY_DROP) + if (gBattleMons[ctx->battlerAtk].volatiles.semiInvulnerable == STATE_SKY_DROP_TARGET) { gBattlescriptCurrInstr = BattleScript_MoveEnd; return CANCELER_RESULT_FAILURE; @@ -80,7 +80,7 @@ static enum CancelerResult CancelerRecharge(struct BattleContext *ctx) { if (gBattleMons[ctx->battlerAtk].volatiles.rechargeTimer > 0) { - CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(ctx->battlerAtk); gBattlescriptCurrInstr = BattleScript_MoveUsedMustRecharge; return CANCELER_RESULT_FAILURE; } @@ -251,7 +251,7 @@ static enum CancelerResult CancelerTruant(struct BattleContext *ctx) { if (GetBattlerAbility(ctx->battlerAtk) == ABILITY_TRUANT && gBattleMons[ctx->battlerAtk].volatiles.truantCounter) { - CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(ctx->battlerAtk); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_LOAFING; gBattlerAbility = ctx->battlerAtk; gBattlescriptCurrInstr = BattleScript_TruantLoafingAround; @@ -273,7 +273,7 @@ static enum CancelerResult CancelerFocus(struct BattleContext *ctx) && (focusPunchFailureConfig == GEN_5 || focusPunchFailureConfig == GEN_6 || GetMoveEffect(ctx->move) == EFFECT_FOCUS_PUNCH) && !gProtectStructs[ctx->battlerAtk].survivedOHKO) { - CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(ctx->battlerAtk); gBattlescriptCurrInstr = BattleScript_FocusPunchLostFocus; return CANCELER_RESULT_FAILURE; } @@ -298,7 +298,7 @@ static enum CancelerResult CancelerFlinch(struct BattleContext *ctx) { if (gBattleMons[ctx->battlerAtk].volatiles.flinched) { - CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(ctx->battlerAtk); gBattlescriptCurrInstr = BattleScript_MoveUsedFlinched; return CANCELER_RESULT_FAILURE; } @@ -312,7 +312,7 @@ static enum CancelerResult CancelerDisabled(struct BattleContext *ctx) && gBattleMons[ctx->battlerAtk].volatiles.disabledMove != MOVE_NONE) { gBattleScripting.battler = ctx->battlerAtk; - CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(ctx->battlerAtk); gBattlescriptCurrInstr = BattleScript_MoveUsedIsDisabled; return CANCELER_RESULT_FAILURE; } @@ -326,20 +326,20 @@ static enum CancelerResult CancelerVolatileBlocked(struct BattleContext *ctx) && IsHealBlockPreventingMove(ctx->battlerAtk, ctx->move)) { gBattleScripting.battler = ctx->battlerAtk; - CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(ctx->battlerAtk); gBattlescriptCurrInstr = BattleScript_MoveUsedHealBlockPrevents; return CANCELER_RESULT_FAILURE; } else if (gFieldStatuses & STATUS_FIELD_GRAVITY && IsGravityPreventingMove(ctx->move)) { gBattleScripting.battler = ctx->battlerAtk; - CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(ctx->battlerAtk); gBattlescriptCurrInstr = BattleScript_MoveUsedGravityPrevents; return CANCELER_RESULT_FAILURE; } else if (GetActiveGimmick(ctx->battlerAtk) != GIMMICK_Z_MOVE && gBattleMons[ctx->battlerAtk].volatiles.throatChopTimer > 0 && IsSoundMove(ctx->move)) { - CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(ctx->battlerAtk); gBattlescriptCurrInstr = BattleScript_MoveUsedIsThroatChopPrevented; return CANCELER_RESULT_FAILURE; } @@ -353,7 +353,7 @@ static enum CancelerResult CancelerTaunted(struct BattleContext *ctx) && IsBattleMoveStatus(ctx->move) && (GetConfig(B_TAUNT_ME_FIRST) < GEN_5 || GetMoveEffect(ctx->move) != EFFECT_ME_FIRST)) { - CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(ctx->battlerAtk); gBattlescriptCurrInstr = BattleScript_MoveUsedIsTaunted; return CANCELER_RESULT_FAILURE; } @@ -364,7 +364,7 @@ static enum CancelerResult CancelerImprisoned(struct BattleContext *ctx) { if (GetActiveGimmick(ctx->battlerAtk) != GIMMICK_Z_MOVE && GetImprisonedMovesCount(ctx->battlerAtk, ctx->move)) { - CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(ctx->battlerAtk); gBattlescriptCurrInstr = BattleScript_MoveUsedIsImprisoned; return CANCELER_RESULT_FAILURE; } @@ -433,7 +433,7 @@ static enum CancelerResult CancelerParalyzed(struct BattleContext *ctx) && !(B_MAGIC_GUARD == GEN_4 && IsAbilityAndRecord(ctx->battlerAtk, ctx->abilityAtk, ABILITY_MAGIC_GUARD)) && !RandomPercentage(RNG_PARALYSIS, 75)) { - CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(gBattlerAttacker); gBattlescriptCurrInstr = BattleScript_MoveUsedIsParalyzed; return CANCELER_RESULT_FAILURE; } @@ -453,7 +453,7 @@ static enum CancelerResult CancelerInfatuation(struct BattleContext *ctx) else { BattleScriptPush(BattleScript_MoveUsedIsInLoveCantAttack); - CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(ctx->battlerAtk); gBattlescriptCurrInstr = BattleScript_MoveUsedIsInLove; return CANCELER_RESULT_FAILURE; } @@ -1015,7 +1015,7 @@ static enum CancelerResult CancelerSkyBattle(struct BattleContext *ctx) { if (gBattleStruct->isSkyBattle && IsMoveSkyBattleBanned(gCurrentMove)) { - CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(ctx->battlerAtk); gBattlescriptCurrInstr = BattleScript_ButItFailed; return CANCELER_RESULT_FAILURE; } @@ -1042,7 +1042,7 @@ static enum CancelerResult CancelerWeatherPrimal(struct BattleContext *ctx) if (result == CANCELER_RESULT_FAILURE) { gProtectStructs[ctx->battlerAtk].chargingTurn = FALSE; - CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(ctx->battlerAtk); gBattlescriptCurrInstr = BattleScript_PrimalWeatherBlocksMove; } } @@ -1492,17 +1492,16 @@ static enum CancelerResult HandleSkyDropResult(struct BattleContext *ctx) gBattleScripting.animTargetsHit = 0; gBattleMons[ctx->battlerAtk].volatiles.multipleTurns = FALSE; gBattleMons[ctx->battlerAtk].volatiles.semiInvulnerable = STATE_NONE; - gBattleMons[ctx->battlerDef].volatiles.semiInvulnerable = STATE_NONE; - gBattleStruct->skyDropTargets[gBattlerTarget] = SKY_DROP_NO_TARGET; + gBattleMons[ctx->battlerAtk].volatiles.skyDropTarget = 0; - // Sky Drop fails if target already fainted - if (gBattleStruct->skyDropTargets[ctx->battlerAtk] == SKY_DROP_NO_TARGET) + // Sky Drop fails if target already left the field + if (gBattleMons[ctx->battlerDef].volatiles.semiInvulnerable == STATE_NONE) { gBattlescriptCurrInstr = BattleScript_SkyDropNoTarget; return CANCELER_RESULT_FAILURE; } - gBattleStruct->skyDropTargets[ctx->battlerAtk] = SKY_DROP_NO_TARGET; + gBattleMons[ctx->battlerDef].volatiles.semiInvulnerable = STATE_NONE; return CANCELER_RESULT_SUCCESS; } @@ -1528,22 +1527,19 @@ static enum CancelerResult HandleSkyDropResult(struct BattleContext *ctx) } // First turn - gBattleMons[ctx->battlerDef].volatiles.multipleTurns = 0; - gBattleMons[ctx->battlerDef].volatiles.uproarTurns = 0; - gBattleMons[ctx->battlerDef].volatiles.bideTurns = 0; - gBattleMons[ctx->battlerDef].volatiles.rolloutTimer = 0; - gBattleMons[ctx->battlerDef].volatiles.furyCutterCounter = 0; - if (gSideTimers[GetBattlerSide(ctx->battlerDef)].followmeTimer != 0 && gSideTimers[GetBattlerSide(ctx->battlerDef)].followmeTarget == ctx->battlerDef) - gSideTimers[GetBattlerSide(ctx->battlerDef)].followmeTimer = 0; + if (gBattleMons[ctx->battlerDef].volatiles.rampageTurns > 0) + gBattleMons[ctx->battlerDef].volatiles.confuseAfterDrop = TRUE; - gBattleStruct->skyDropTargets[ctx->battlerAtk] = ctx->battlerDef; - gBattleStruct->skyDropTargets[ctx->battlerDef] = ctx->battlerAtk; + CancelMultiTurnMoves(ctx->battlerDef); gLockedMoves[ctx->battlerAtk] = ctx->move; gProtectStructs[ctx->battlerAtk].chargingTurn = TRUE; gBattleMons[ctx->battlerAtk].volatiles.multipleTurns = TRUE; - gBattleMons[ctx->battlerAtk].volatiles.semiInvulnerable = STATE_ON_AIR; - gBattleMons[ctx->battlerDef].volatiles.semiInvulnerable = STATE_SKY_DROP; + gBattleMons[ctx->battlerAtk].volatiles.skyDropTarget = ctx->battlerDef + 1; + gBattleMons[ctx->battlerAtk].volatiles.semiInvulnerable = STATE_SKY_DROP_ATTACKER; + gBattleMons[ctx->battlerDef].volatiles.semiInvulnerable = STATE_SKY_DROP_TARGET; + if (gSideTimers[GetBattlerSide(ctx->battlerDef)].followmeTimer != 0 && gSideTimers[GetBattlerSide(ctx->battlerDef)].followmeTarget == ctx->battlerDef) + gSideTimers[GetBattlerSide(ctx->battlerDef)].followmeTimer = 0; gBattlescriptCurrInstr = BattleScript_SkyDropCharging; return CANCELER_RESULT_BREAK; @@ -1803,7 +1799,7 @@ static enum CancelerResult CancelerTargetFailure(struct BattleContext *ctx) gLastHitByType[gBattlerTarget] = 0; gBattleScripting.battler = ctx->battlerDef; gBattleStruct->pledgeMove = FALSE; - CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); + CancelMultiTurnMoves(ctx->battlerAtk); return CANCELER_RESULT_PAUSE; } } @@ -2343,7 +2339,7 @@ static enum MoveEndResult MoveEndAttackerVisible(void) if (IsBattlerUnaffectedByMove(gBattlerTarget) || !IsSemiInvulnerable(gBattlerAttacker, CHECK_ALL) - || (gBattleStruct->unableToUseMove && gBattleMons[gBattlerAttacker].volatiles.semiInvulnerable != STATE_SKY_DROP)) + || (gBattleStruct->unableToUseMove && gBattleMons[gBattlerAttacker].volatiles.semiInvulnerable != STATE_SKY_DROP_TARGET)) { BtlController_EmitSpriteInvisibility(gBattlerAttacker, B_COMM_TO_CONTROLLER, FALSE); MarkBattlerForControllerExec(gBattlerAttacker); @@ -2574,37 +2570,6 @@ static enum MoveEndResult MoveEndFaintBlock(void) return result; } -static enum MoveEndResult MoveEndSkyDropConfuse(void) -{ - enum MoveEndResult result = MOVEEND_RESULT_CONTINUE; - - for (enum BattlerId battler = 0; battler < gBattlersCount; battler++) - { - if (gBattleStruct->skyDropTargets[battler] == SKY_DROP_RELEASED_TARGET) - { - // Find the battler id of the Pokemon that was held by Sky Drop - enum BattlerId targetId; - for (targetId = 0; targetId < gBattlersCount; targetId++) - { - if (gBattleStruct->skyDropTargets[targetId] == battler) - break; - } - - gBattleScripting.battler = targetId; - BattleScriptCall(BattleScript_ThrashConfusesRet); // Jump to "confused due to fatigue" script - - // Clear skyDropTargets data - gBattleStruct->skyDropTargets[battler] = SKY_DROP_NO_TARGET; - gBattleStruct->skyDropTargets[targetId] = SKY_DROP_NO_TARGET; - result = MOVEEND_RESULT_RUN_SCRIPT; - break; - } - } - - gBattleScripting.moveendState++; - return result; -} - static enum MoveEndResult MoveEndUpdateLastMoves(void) { if (!IsOnPlayerSide(gBattlerAttacker)) @@ -3654,6 +3619,58 @@ static bool32 ShouldSetStompingTantrumTimer(void) return numNotAffectedTargets == gBattleStruct->numSpreadTargets; } +static enum MoveEndResult MoveEndRampage(void) +{ + enum MoveEndResult result = MOVEEND_RESULT_CONTINUE; + + if (gBattleMons[gBattlerAttacker].volatiles.rampageTurns == 0 + || gSpecialStatuses[gBattlerAttacker].dancerUsedMove + || B_RAMPAGE_CONFUSION < GEN_5) + { + result = MOVEEND_RESULT_CONTINUE; + } + else if (--gBattleMons[gBattlerAttacker].volatiles.rampageTurns == 0) + { + CancelMultiTurnMoves(gBattlerAttacker); + if (CanBeConfused(gBattlerAttacker, gBattlerAttacker)) + { + gBattleScripting.battler = gBattlerAttacker; + gBattleMons[gBattlerAttacker].volatiles.confusionTurns = RandomUniform(RNG_CONFUSION_TURNS, 2, B_CONFUSION_TURNS); // 2-5 turns + BattleScriptCall(BattleScript_ConfusionAfterRampage); + result = MOVEEND_RESULT_BREAK; + } + } + else if (IsBattlerUnaffectedByMove(gBattlerTarget)) + { + CancelMultiTurnMoves(gBattlerAttacker); + result = MOVEEND_RESULT_CONTINUE; + } + + gBattleScripting.moveendState++; + return result; +} + +static enum MoveEndResult MoveEndConfusionAfterSkyDrop(void) +{ + enum MoveEndResult result = MOVEEND_RESULT_CONTINUE; + + if (gBattleMons[gBattlerTarget].volatiles.confuseAfterDrop + && gBattleMons[gBattlerTarget].volatiles.semiInvulnerable != STATE_SKY_DROP_TARGET) + { + gBattleMons[gBattlerTarget].volatiles.confuseAfterDrop = FALSE; + if (CanBeConfused(gBattlerTarget, gBattlerTarget)) + { + gBattleScripting.battler = gBattlerTarget; + gBattleMons[gBattlerTarget].volatiles.confusionTurns = RandomUniform(RNG_CONFUSION_TURNS, 2, B_CONFUSION_TURNS); // 2-5 turns + BattleScriptCall(BattleScript_ConfusionAfterRampage); + result = MOVEEND_RESULT_BREAK; + } + } + + gBattleScripting.moveendState++; + return result; +} + static enum MoveEndResult MoveEndClearBits(void) { ValidateBattlers(); @@ -3672,12 +3689,6 @@ static enum MoveEndResult MoveEndClearBits(void) if (originallyUsedMove != MOVE_NONE) TryUpdateEvolutionTracker(IF_USED_MOVE_X_TIMES, 1, originallyUsedMove); - if (B_RAMPAGE_CANCELLING >= GEN_5 - && MoveHasAdditionalEffectSelf(gCurrentMove, MOVE_EFFECT_THRASH) // If we're rampaging - && IsBattlerUnaffectedByMove(gBattlerTarget) // And it is unusable - && gBattleMons[gBattlerAttacker].volatiles.rampageTurns != 1) // And won't end this turn - CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_IGNORE); // Cancel it - SetSameMoveTurnValues(moveEffect); TryClearChargeVolatile(moveType); gProtectStructs[gBattlerAttacker].shellTrap = FALSE; @@ -3699,6 +3710,7 @@ static enum MoveEndResult MoveEndClearBits(void) SetActiveGimmick(gBattlerAttacker, GIMMICK_NONE); if (gBattleMons[gBattlerAttacker].volatiles.destinyBond > 0) gBattleMons[gBattlerAttacker].volatiles.destinyBond--; + // check if Stellar type boost should be used up if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA && GetBattlerTeraType(gBattlerAttacker) == TYPE_STELLAR @@ -3789,7 +3801,6 @@ static enum MoveEndResult (*const sMoveEndHandlers[])(void) = [MOVEEND_SYMBIOSIS] = MoveEndSymbiosis, [MOVEEND_SUBSTITUTE] = MoveEndSubstitute, [MOVEEND_FAINT_BLOCK] = MoveEndFaintBlock, - [MOVEEND_SKY_DROP_CONFUSE] = MoveEndSkyDropConfuse, [MOVEEND_UPDATE_LAST_MOVES] = MoveEndUpdateLastMoves, [MOVEEND_MIRROR_MOVE] = MoveEndMirrorMove, [MOVEEND_NEXT_TARGET] = MoveEndNextTarget, @@ -3807,14 +3818,16 @@ static enum MoveEndResult (*const sMoveEndHandlers[])(void) = [MOVEEND_LIFE_ORB_SHELL_BELL] = MoveEndLifeOrbShellBell, [MOVEEND_FORM_CHANGE] = MoveEndFormChange, [MOVEEND_EMERGENCY_EXIT] = MoveEndEmergencyExit, - [MOVEEND_EJECT_PACK] = MoveEndEjectPack, [MOVEEND_HIT_ESCAPE] = MoveEndHitEscape, + [MOVEEND_PICKPOCKET] = MoveEndPickpocket, [MOVEEND_ITEMS_EFFECTS_ALL] = MoveEndItemsEffectsAll, [MOVEEND_WHITE_HERB] = MoveEndWhiteHerb, [MOVEEND_OPPORTUNIST] = MoveEndOpportunist, [MOVEEND_MIRROR_HERB] = MoveEndMirrorHerb, [MOVEEND_THIRD_MOVE_BLOCK] = MoveEndThirdMoveBlock, - [MOVEEND_PICKPOCKET] = MoveEndPickpocket, + [MOVEEND_RAMPAGE] = MoveEndRampage, + [MOVEEND_CONFUSION_AFTER_SKY_DROP] = MoveEndConfusionAfterSkyDrop, + [MOVEEND_EJECT_PACK] = MoveEndEjectPack, [MOVEEND_CLEAR_BITS] = MoveEndClearBits, [MOVEEND_DANCER] = MoveEndDancer, [MOVEEND_PURSUIT_NEXT_ACTION] = MoveEndPursuitNextAction, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 13ad9a7d5a..4bbe671655 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -576,6 +576,7 @@ static void Cmd_jumpifcaptivateaffected(void); static void Cmd_setnonvolatilestatus(void); static void Cmd_tryoverwriteability(void); static void Cmd_trysynchronize(void); +static void Cmd_tryconfusionafterskydrop(void); static void Cmd_dummy(void); static void Cmd_callnative(void); @@ -804,6 +805,7 @@ void (*const gBattleScriptingCommandsTable[])(void) = [B_SCR_OP_SETNONVOLATILESTATUS] = Cmd_setnonvolatilestatus, [B_SCR_OP_TRYOVERWRITEABILITY] = Cmd_tryoverwriteability, [B_SCR_OP_TRY_SYNCHRONIZE] = Cmd_trysynchronize, + [B_SCR_OP_TRY_CONFUSION_AFTER_SKY_DROP] = Cmd_tryconfusionafterskydrop, [B_SCR_OP_UNUSED_1] = Cmd_dummy, [B_SCR_OP_UNUSED_2] = Cmd_dummy, [B_SCR_OP_UNUSED_3] = Cmd_dummy, @@ -835,7 +837,6 @@ void (*const gBattleScriptingCommandsTable[])(void) = [B_SCR_OP_UNUSED_29] = Cmd_dummy, [B_SCR_OP_UNUSED_30] = Cmd_dummy, [B_SCR_OP_UNUSED_31] = Cmd_dummy, - [B_SCR_OP_UNUSED_32] = Cmd_dummy, [B_SCR_OP_CALLNATIVE] = Cmd_callnative, }; @@ -2321,14 +2322,8 @@ static void SetNonVolatileStatus(enum BattlerId battlerAtk, enum BattlerId effec { gEffectBattler = effectBattler; - if (effect == MOVE_EFFECT_SLEEP - || effect == MOVE_EFFECT_FREEZE) - { - const u8 *cancelMultiTurnMovesResult = NULL; - cancelMultiTurnMovesResult = CancelMultiTurnMoves(effectBattler, SKY_DROP_STATUS_FREEZE_SLEEP); - if (cancelMultiTurnMovesResult) - gBattlescriptCurrInstr = cancelMultiTurnMovesResult; - } + if (effect == MOVE_EFFECT_SLEEP || effect == MOVE_EFFECT_FREEZE) + CancelMultiTurnMoves(effectBattler); BattleScriptPush(battleScript); @@ -2518,29 +2513,15 @@ void SetMoveEffect(enum BattlerId battlerAtk, enum BattlerId effectBattler, enum } break; case MOVE_EFFECT_CONFUSION: - if (!CanBeConfused(gEffectBattler) - || gBattleMons[gEffectBattler].volatiles.confusionTurns - || (IsSafeguardProtected(gBattlerAttacker, gEffectBattler, GetBattlerAbility(gBattlerAttacker)) && !primary)) + if (!CanBeConfused(gBattlerAttacker, gEffectBattler)) { gBattlescriptCurrInstr = battleScript; } else { gBattleMons[gEffectBattler].volatiles.confusionTurns = RandomUniform(RNG_CONFUSION_TURNS, 2, B_CONFUSION_TURNS); // 2-5 turns - - // If the confusion is activating due to being released from Sky Drop, go to "confused due to fatigue" script. - // Otherwise, do normal confusion script. - if (GetMoveEffect(gCurrentMove) == EFFECT_SKY_DROP) - { - gBattleMons[gEffectBattler].volatiles.rampageTurns = 0; - gBattlerAttacker = gEffectBattler; - gBattlescriptCurrInstr = BattleScript_ThrashConfuses; - } - else - { - BattleScriptPush(battleScript); - gBattlescriptCurrInstr = BattleScript_MoveEffectConfusion; - } + BattleScriptPush(battleScript); + gBattlescriptCurrInstr = BattleScript_MoveEffectConfusion; } break; case MOVE_EFFECT_FLINCH: @@ -3184,8 +3165,8 @@ void SetMoveEffect(enum BattlerId battlerAtk, enum BattlerId effectBattler, enum MarkBattlerForControllerExec(gBattlerTarget); } - if (gBattleMons[gBattlerTarget].pp[i] == 0 && gBattleStruct->skyDropTargets[gBattlerTarget] == SKY_DROP_NO_TARGET) - CancelMultiTurnMoves(gBattlerTarget, SKY_DROP_IGNORE); + if (gBattleMons[gBattlerTarget].pp[i] == 0) + CancelMultiTurnMoves(gBattlerTarget); BattleScriptPush(battleScript); gBattlescriptCurrInstr = BattleScript_MoveEffectEerieSpell; @@ -3977,7 +3958,6 @@ static void Cmd_cleareffectsonfaint(void) if (gBattleControllerExecFlags == 0) { enum BattlerId battler = GetBattlerForBattleScript(cmd->battler); - const u8 *clearDataResult = NULL; if (!(gBattleTypeFlags & BATTLE_TYPE_ARENA) || !IsBattlerAlive(battler)) { gBattleMons[battler].status1 = 0; @@ -3985,11 +3965,8 @@ static void Cmd_cleareffectsonfaint(void) MarkBattlerForControllerExec(battler); } - clearDataResult = FaintClearSetData(battler); // Effects like attractions, trapping, etc. - if (clearDataResult) - gBattlescriptCurrInstr = clearDataResult; - else - gBattlescriptCurrInstr = cmd->nextInstr; + FaintClearSetData(battler); // Effects like attractions, trapping, etc. + gBattlescriptCurrInstr = cmd->nextInstr; } } @@ -9100,9 +9077,8 @@ static void Cmd_tryspiteppreduce(void) gBattlescriptCurrInstr = cmd->nextInstr; - // Don't cut off Sky Drop if pp is brought to zero. - if (gBattleMons[gBattlerTarget].pp[i] == 0 && gBattleStruct->skyDropTargets[gBattlerTarget] == SKY_DROP_NO_TARGET) - CancelMultiTurnMoves(gBattlerTarget, SKY_DROP_IGNORE); + if (gBattleMons[gBattlerTarget].pp[i] == 0) + CancelMultiTurnMoves(gBattlerTarget); } else { @@ -11632,6 +11608,43 @@ static void Cmd_trysynchronize(void) } } +// If target was dropped due to attacker fainting and was previously rampaging, try to confuse +static void Cmd_tryconfusionafterskydrop(void) +{ + CMD_ARGS(u8 battler); + enum BattlerId faintBattler = GetBattlerForBattleScript(cmd->battler); + enum BattlerId skyDropTarget = gBattleMons[faintBattler].volatiles.skyDropTarget - 1; + bool32 shouldConfuse = FALSE; + + if (gBattleMons[faintBattler].volatiles.semiInvulnerable != STATE_SKY_DROP_ATTACKER) + { + gBattlescriptCurrInstr = cmd->nextInstr; + } + else if (gBattleMons[skyDropTarget].volatiles.semiInvulnerable == STATE_SKY_DROP_TARGET) + { + BtlController_EmitSpriteInvisibility(skyDropTarget, B_COMM_TO_CONTROLLER, FALSE); + MarkBattlerForControllerExec(skyDropTarget); + gBattleMons[skyDropTarget].volatiles.semiInvulnerable = STATE_NONE; + gSpecialStatuses[skyDropTarget].restoredBattlerSprite = TRUE; + + if (gBattleMons[skyDropTarget].volatiles.confuseAfterDrop) + { + gBattleMons[skyDropTarget].volatiles.confuseAfterDrop = FALSE; + + if (CanBeConfused(skyDropTarget, skyDropTarget)) + { + shouldConfuse = TRUE; + gBattleScripting.battler = skyDropTarget; + BattleScriptPush(cmd->nextInstr); + gBattlescriptCurrInstr = BattleScript_ConfusionAfterRampage; + } + } + } + + if (!shouldConfuse) + gBattlescriptCurrInstr = cmd->nextInstr; +} + static void Cmd_dummy(void) { } @@ -13172,7 +13185,7 @@ void BS_TrySetConfusion(void) { NATIVE_ARGS(const u8 *failInstr); - if (CanBeConfused(gBattlerTarget)) + if (CanBeConfused(gBattlerAttacker, gBattlerTarget)) { gBattleMons[gBattlerTarget].volatiles.confusionTurns = RandomUniform(RNG_CONFUSION_TURNS, 2, B_CONFUSION_TURNS); // 2-5 turns gBattleCommunication[MULTIUSE_STATE] = 1; @@ -13612,10 +13625,6 @@ void BS_JumpIfNotBerry(void) void BS_GravityOnAirborneMons(void) { NATIVE_ARGS(); - // Cancel all multiturn moves of IN_AIR Pokemon except those being targeted by Sky Drop. - if (gBattleMons[gBattlerTarget].volatiles.semiInvulnerable == STATE_ON_AIR) - CancelMultiTurnMoves(gBattlerTarget, SKY_DROP_GRAVITY_ON_AIRBORNE); - gBattleMons[gBattlerTarget].volatiles.semiInvulnerable = STATE_NONE; gBattleMons[gBattlerTarget].volatiles.magnetRise = FALSE; gBattleMons[gBattlerTarget].volatiles.telekinesis = FALSE; @@ -13651,11 +13660,8 @@ void BS_TryAcupressure(void) void BS_CancelMultiTurnMoves(void) { NATIVE_ARGS(); - const u8 *result = CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_CANCEL_MULTI_TURN_MOVES); - if (result) - gBattlescriptCurrInstr = result; - else - gBattlescriptCurrInstr = cmd->nextInstr; + CancelMultiTurnMoves(gBattlerAttacker); + gBattlescriptCurrInstr = cmd->nextInstr; } void BS_IsRunningImpossible(void) @@ -14219,7 +14225,7 @@ void BS_TryInstruct(void) || IsMoveInstructBanned(move) || IsInstructBannedChargingMove(gBattlerTarget) || gBattleMons[gBattlerTarget].volatiles.bideTurns != 0 - || gBattleMons[gBattlerTarget].volatiles.semiInvulnerable == STATE_SKY_DROP + || gBattleMons[gBattlerTarget].volatiles.semiInvulnerable == STATE_SKY_DROP_TARGET || gBattleMoveEffects[GetMoveEffect(move)].twoTurnEffect || (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX) || IsZMove(move) @@ -14643,37 +14649,6 @@ void BS_TryHealQuarterHealth(void) gBattlescriptCurrInstr = cmd->nextInstr; // can heal } -void BS_SkyDropYawn(void) -{ - NATIVE_ARGS(); - if (gBattleStruct->skyDropTargets[gEffectBattler] != SKY_DROP_NO_TARGET && gBattleMons[gEffectBattler].volatiles.semiInvulnerable != STATE_SKY_DROP) - { - // Set the target of Sky Drop as gEffectBattler - gEffectBattler = gBattleStruct->skyDropTargets[gEffectBattler]; - - // Clear skyDropTargets data - gBattleStruct->skyDropTargets[gBattleStruct->skyDropTargets[gEffectBattler]] = SKY_DROP_NO_TARGET; - gBattleStruct->skyDropTargets[gEffectBattler] = SKY_DROP_NO_TARGET; - - // If the target was in the middle of Outrage/Thrash/etc. when targeted by Sky Drop, confuse them on release and do proper animation - if (gBattleMons[gEffectBattler].volatiles.rampageTurns && CanBeConfused(gEffectBattler)) - { - gBattleMons[gEffectBattler].volatiles.rampageTurns = 0; - gBattlerAttacker = gEffectBattler; - gBattleMons[gBattlerTarget].volatiles.confusionTurns = RandomUniform(RNG_CONFUSION_TURNS, 2, B_CONFUSION_TURNS); // 2-5 turns - gBattlescriptCurrInstr = BattleScript_ThrashConfuses; - } - else - { - gBattlescriptCurrInstr = cmd->nextInstr; - } - } - else - { - gBattlescriptCurrInstr = cmd->nextInstr; - } -} - void BS_JumpIfPranksterBlocked(void) { NATIVE_ARGS(const u8 *jumpInstr); diff --git a/src/battle_util.c b/src/battle_util.c index e3398ddc96..8a829b73ad 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -1072,96 +1072,26 @@ void MarkBattlerReceivedLinkData(enum BattlerId battler) MarkBattleControllerMessageSynchronizedOverLink(battler); } -const u8 *CheckSkyDropState(enum BattlerId battler, enum SkyDropState skyDropState) +void CancelMultiTurnMoves(enum BattlerId battler) { - const u8 *result = NULL; - - u8 otherSkyDropper = gBattleStruct->skyDropTargets[battler]; - gBattleMons[battler].volatiles.semiInvulnerable = STATE_NONE; - - // Makes both attacker and target's sprites visible - gSprites[gBattlerSpriteIds[battler]].invisible = FALSE; - gSprites[gBattlerSpriteIds[otherSkyDropper]].invisible = FALSE; - - // If target was sky dropped in the middle of Outrage/Thrash/Petal Dance, - // confuse them upon release and display "confused by fatigue" message & animation. - // Don't do this if this CancelMultiTurnMoves is caused by falling asleep via Yawn. - if (gBattleMons[otherSkyDropper].volatiles.rampageTurns && skyDropState != SKY_DROP_STATUS_YAWN) - { - gBattleMons[otherSkyDropper].volatiles.rampageTurns = 0; - - // If the target can be confused, confuse them. - // Don't use CanBeConfused, can cause issues in edge cases. - enum Ability ability = GetBattlerAbility(otherSkyDropper); - if (!(gBattleMons[otherSkyDropper].volatiles.confusionTurns > 0 - || IsAbilityAndRecord(otherSkyDropper, ability, ABILITY_OWN_TEMPO) - || IsMistyTerrainAffected(otherSkyDropper, ability, GetBattlerHoldEffect(otherSkyDropper), gFieldStatuses))) - { - gBattleMons[otherSkyDropper].volatiles.confusionTurns = RandomUniform(RNG_CONFUSION_TURNS, 2, B_CONFUSION_TURNS); // 2-5 turns - if (skyDropState == SKY_DROP_ATTACKCANCELER_CHECK) - { - gBattleStruct->skyDropTargets[battler] = SKY_DROP_RELEASED_TARGET; - } - else if (skyDropState == SKY_DROP_GRAVITY_ON_AIRBORNE) - { - // Reapplying STATE_SKY_DROPPED allows for avoiding unecessary messages when Gravity is applied to the target. - gBattleStruct->skyDropTargets[battler] = SKY_DROP_RELEASED_TARGET; - gBattleMons[otherSkyDropper].volatiles.semiInvulnerable = STATE_SKY_DROP; - } - else if (skyDropState == SKY_DROP_CANCEL_MULTI_TURN_MOVES) - { - gBattlerAttacker = otherSkyDropper; - result = BattleScript_ThrashConfuses; - } - else if (skyDropState == SKY_DROP_STATUS_FREEZE_SLEEP) - { - gBattlerAttacker = otherSkyDropper; - BattleScriptPush(gBattlescriptCurrInstr + 1); - result = BattleScript_ThrashConfuses; - } - } - } - - // Clear skyDropTargets data, unless this CancelMultiTurnMoves is caused by Yawn, attackcanceler, or VARIOUS_GRAVITY_ON_AIRBORNE_MONS - if (!(gBattleMons[otherSkyDropper].volatiles.rampageTurns) && gBattleStruct->skyDropTargets[battler] < 4) - { - gBattleStruct->skyDropTargets[battler] = SKY_DROP_NO_TARGET; - gBattleStruct->skyDropTargets[otherSkyDropper] = SKY_DROP_NO_TARGET; - } - - return result; -} - -const u8 *CancelMultiTurnMoves(enum BattlerId battler, enum SkyDropState skyDropState) -{ - const u8 *result = NULL; gBattleMons[battler].volatiles.uproarTurns = 0; gBattleMons[battler].volatiles.bideTurns = 0; - - if (B_RAMPAGE_CANCELLING < GEN_5) - { - gBattleMons[battler].volatiles.multipleTurns = 0; - gBattleMons[battler].volatiles.rampageTurns = 0; - } - else if (!gBattleMons[battler].volatiles.rampageTurns - || gBattleMons[battler].volatiles.rampageTurns > 1) - { - gBattleMons[battler].volatiles.multipleTurns = 0; - } - - // Clear battler's semi-invulnerable bits if they are not held by Sky Drop. - if (gBattleMons[battler].volatiles.semiInvulnerable != STATE_SKY_DROP) - gBattleMons[battler].volatiles.semiInvulnerable = STATE_NONE; - - if (gBattleStruct->skyDropTargets[battler] != SKY_DROP_NO_TARGET && gBattleMons[battler].volatiles.semiInvulnerable != STATE_SKY_DROP) - result = CheckSkyDropState(battler, skyDropState); - gBattleMons[battler].volatiles.rolloutTimer = 0; gBattleMons[battler].volatiles.furyCutterCounter = 0; - return result; -} + if (B_RAMPAGE_CONFUSION < GEN_5 + || gBattleMons[battler].volatiles.rampageTurns != 1) // Will be confused at the end of the turn + { + gLockedMoves[battler] = MOVE_NONE; + gBattleMons[battler].volatiles.multipleTurns = 0; + gBattleMons[battler].volatiles.rampageTurns = 0; + } + // Clear battler's semi-invulnerable bits if they are not held by Sky Drop. + if (gBattleMons[battler].volatiles.semiInvulnerable != STATE_SKY_DROP_TARGET) + gBattleMons[battler].volatiles.semiInvulnerable = STATE_NONE; + +} // Returns TRUE if no other battler after this one in turn order will use a move bool32 IsLastMonToMove(enum BattlerId battler) @@ -1425,7 +1355,7 @@ u32 TrySetCantSelectMoveBattleScript(enum BattlerId battler) if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && move == gLastMoves[battler] && move != MOVE_STRUGGLE && (gBattleMons[battler].volatiles.torment == TRUE)) { - CancelMultiTurnMoves(battler, SKY_DROP_IGNORE); + CancelMultiTurnMoves(battler); if (gBattleTypeFlags & BATTLE_TYPE_PALACE) { gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingTormentedMoveInPalace; @@ -1808,7 +1738,7 @@ bool32 BattleArenaTurnEnd(void) && IsBattlerAlive(GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)) && IsBattlerAlive(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT))) { for (enum BattlerId battler = 0; battler < 2; battler++) - CancelMultiTurnMoves(battler, SKY_DROP_IGNORE); + CancelMultiTurnMoves(battler); gBattlescriptCurrInstr = BattleScript_ArenaDoJudgment; BattleScriptExecute(BattleScript_ArenaDoJudgment); @@ -4418,7 +4348,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum case ABILITY_POISON_PUPPETEER: if (IsRestrictedAbility(gBattlerAttacker, ABILITY_POISON_PUPPETEER) && gBattleStruct->poisonPuppeteerConfusion == TRUE - && CanBeConfused(gBattlerTarget)) + && CanBeConfused(gBattlerAttacker, gBattlerTarget)) { gBattleStruct->poisonPuppeteerConfusion = FALSE; gBattleScripting.moveEffect = MOVE_EFFECT_CONFUSION; @@ -5023,7 +4953,7 @@ bool32 CanBattlerEscape(enum BattlerId battler) // no ability check return FALSE; else if (gFieldStatuses & STATUS_FIELD_FAIRY_LOCK) return FALSE; - else if (gBattleMons[battler].volatiles.semiInvulnerable == STATE_SKY_DROP) + else if (gBattleMons[battler].volatiles.semiInvulnerable == STATE_SKY_DROP_TARGET) return FALSE; else return TRUE; @@ -5512,13 +5442,16 @@ static bool32 CanSleepDueToSleepClause(enum BattlerId battlerAtk, enum BattlerId return FALSE; } -bool32 CanBeConfused(enum BattlerId battler) +bool32 CanBeConfused(enum BattlerId battlerAtk, enum BattlerId effectBattler) { - enum Ability ability = GetBattlerAbility(battler); - if (gBattleMons[battler].volatiles.confusionTurns > 0 - || IsMistyTerrainAffected(battler, ability, GetBattlerHoldEffect(battler), gFieldStatuses) - || IsAbilityAndRecord(battler, ability, ABILITY_OWN_TEMPO)) + enum Ability effectAbility = GetBattlerAbility(effectBattler); + + if (gBattleMons[effectBattler].volatiles.confusionTurns > 0 + || IsSafeguardProtected(battlerAtk, effectBattler, GetBattlerAbility(battlerAtk)) + || IsMistyTerrainAffected(effectBattler, effectAbility, GetBattlerHoldEffect(effectBattler), gFieldStatuses) + || IsAbilityAndRecord(effectBattler, effectAbility, ABILITY_OWN_TEMPO)) return FALSE; + return TRUE; } @@ -7424,7 +7357,7 @@ static inline uq4_12_t GetDiveModifier(enum Move move, enum BattlerId battlerDef static inline uq4_12_t GetAirborneModifier(enum Move move, enum BattlerId battlerDef) { - if (MoveDamagesAirborneDoubleDamage(move) && gBattleMons[battlerDef].volatiles.semiInvulnerable == STATE_ON_AIR) + if (MoveDamagesAirborneDoubleDamage(move) && IsBattlerOnAir(battlerDef)) return UQ_4_12(2.0); return UQ_4_12(1.0); } @@ -8543,7 +8476,7 @@ bool32 CanMegaEvolve(enum BattlerId battler) return FALSE; // Check if battler is currently held by Sky Drop. - if (gBattleMons[battler].volatiles.semiInvulnerable == STATE_SKY_DROP) + if (gBattleMons[battler].volatiles.semiInvulnerable == STATE_SKY_DROP_TARGET) return FALSE; // Check if battler is holding a Z-Crystal. @@ -8584,7 +8517,7 @@ bool32 CanUltraBurst(enum BattlerId battler) return FALSE; // Check if mon is currently held by Sky Drop - if (gBattleMons[battler].volatiles.semiInvulnerable == STATE_SKY_DROP) + if (gBattleMons[battler].volatiles.semiInvulnerable == STATE_SKY_DROP_TARGET) return FALSE; enum Ability ability = GetBattlerAbility(battler); @@ -10057,7 +9990,7 @@ bool32 EmergencyExitCanBeTriggered(enum BattlerId battler) && HadMoreThanHalfHpNowDoesnt(battler) && (CanBattlerSwitch(battler) || !(gBattleTypeFlags & BATTLE_TYPE_TRAINER)) && !(gBattleTypeFlags & BATTLE_TYPE_ARENA) - && gBattleMons[battler].volatiles.semiInvulnerable != STATE_SKY_DROP) + && gBattleMons[battler].volatiles.semiInvulnerable != STATE_SKY_DROP_TARGET) return TRUE; return FALSE; @@ -10259,6 +10192,14 @@ static bool32 CanMoveSkipAccuracyCheck(enum BattlerId battlerAtk, u32 move) return MoveAlwaysHitsOnSameType(move) && IS_BATTLER_OF_TYPE(battlerAtk, GetMoveType(move)); } +static bool32 IsSkyDropInvolved(enum BattlerId battlerDef, enum BattleMoveEffects moveEffect) +{ + if (moveEffect != EFFECT_SKY_DROP) + return FALSE; + return gBattleMons[battlerDef].volatiles.semiInvulnerable == STATE_SKY_DROP_ATTACKER + || gBattleMons[battlerDef].volatiles.semiInvulnerable == STATE_SKY_DROP_TARGET; +} + bool32 CanMoveSkipAccuracyCalc(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Ability abilityAtk, enum Ability abilityDef, enum Move move, enum ResultOption option) { bool32 effect = FALSE; @@ -10271,17 +10212,14 @@ bool32 CanMoveSkipAccuracyCalc(enum BattlerId battlerAtk, enum BattlerId battler { effect = TRUE; } - // If the attacker has the ability No Guard and they aren't targeting a Pokemon involved in a Sky Drop with the move Sky Drop, move hits. else if (abilityAtk == ABILITY_NO_GUARD && gBattleMons[battlerDef].volatiles.semiInvulnerable != STATE_COMMANDER - && (moveEffect != EFFECT_SKY_DROP || gBattleStruct->skyDropTargets[battlerDef] == SKY_DROP_NO_TARGET)) + && !IsSkyDropInvolved(battlerDef, moveEffect)) { effect = TRUE; ability = ABILITY_NO_GUARD; } - // If the target has the ability No Guard and they aren't involved in a Sky Drop or the current move isn't Sky Drop, move hits. - else if (abilityDef == ABILITY_NO_GUARD - && (moveEffect != EFFECT_SKY_DROP || gBattleStruct->skyDropTargets[battlerDef] == SKY_DROP_NO_TARGET)) + else if (abilityDef == ABILITY_NO_GUARD && !IsSkyDropInvolved(battlerDef, moveEffect)) { effect = TRUE; ability = ABILITY_NO_GUARD; @@ -10569,7 +10507,8 @@ static bool32 CanBreakThroughSemiInvulnerablityInternal(enum BattlerId battlerAt case STATE_UNDERWATER: return MoveDamagesUnderWater(move); case STATE_ON_AIR: - case STATE_SKY_DROP: + case STATE_SKY_DROP_ATTACKER: + case STATE_SKY_DROP_TARGET: return MoveDamagesAirborne(move) || MoveDamagesAirborneDoubleDamage(move); case STATE_PHANTOM_FORCE: return FALSE; @@ -10593,6 +10532,19 @@ bool32 BreaksThroughSemiInvulnerableState(enum BattlerId battlerAtk, enum Battle return CanBreakThroughSemiInvulnerablityInternal(battlerAtk, battlerDef, abilityAtk, abilityDef, move, state); } +bool32 IsBattlerOnAir(enum BattlerId battler) +{ + switch (gBattleMons[battler].volatiles.semiInvulnerable) + { + case STATE_ON_AIR: + case STATE_SKY_DROP_ATTACKER: + case STATE_SKY_DROP_TARGET: + return TRUE; + default: + return FALSE; + } +} + bool32 HasPartnerTrainer(enum BattlerId battler) { if ((GetBattlerSide(battler) == B_SIDE_PLAYER && gBattleTypeFlags & BATTLE_TYPE_PLAYER_HAS_PARTNER) diff --git a/test/battle/move_effect/sky_drop.c b/test/battle/move_effect/sky_drop.c index 08d73010dd..80f91ec2df 100644 --- a/test/battle/move_effect/sky_drop.c +++ b/test/battle/move_effect/sky_drop.c @@ -208,3 +208,86 @@ DOUBLE_BATTLE_TEST("Sky Drop does not trigger Volt Absorb on it's charge turn") } } + +SINGLE_BATTLE_TEST("Sky Drop: If target was locked into a move that would confuse, the target will be freed and confusion occurs immediately") +{ + GIVEN { + PLAYER(SPECIES_TORKOAL) { Ability(ABILITY_DROUGHT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_THRASH); MOVE(opponent, MOVE_SKY_DROP);} + TURN { SKIP_TURN(opponent); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DROUGHT); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player); + MESSAGE("The sunlight is strong."); + } +} + +DOUBLE_BATTLE_TEST("Sky Drop: If target was locked into a move that would confuse, the target will be freed and confusion occurs immediately (attacker faints due to status)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); MaxHP(2); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_THRASH, target: opponentRight); + MOVE(opponentLeft, MOVE_SKY_DROP, target: playerLeft); + SEND_OUT(opponentLeft, 2); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, opponentLeft); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, playerLeft); + } +} + +DOUBLE_BATTLE_TEST("Sky Drop: If target was locked into a move that would confuse, the target will be freed and confusion occurs immediately (attacker faints due to target ability)") +{ + GIVEN { + PLAYER(SPECIES_SHARPEDO) { Ability(ABILITY_ROUGH_SKIN); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); MaxHP(2); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_THRASH, target: opponentRight); + MOVE(opponentLeft, MOVE_SKY_DROP, target: playerLeft); + } + TURN { + SKIP_TURN(opponentLeft); + SEND_OUT(opponentLeft, 2); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, opponentLeft); // 1st turn + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, opponentLeft); // 2nd turn + ABILITY_POPUP(playerLeft, ABILITY_ROUGH_SKIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, playerLeft); + } +} + +SINGLE_BATTLE_TEST("Sky Drop: Flying types will still get confused if they rampaged before being dropped") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_PIDGEY].weight < 2000); + ASSUME(GetSpeciesType(SPECIES_PIDGEY, 1) == TYPE_FLYING); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY); + } WHEN { + TURN { MOVE(opponent, MOVE_THRASH); MOVE(player, MOVE_SKY_DROP); } + TURN { SKIP_TURN(player); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, player); + NOT HP_BAR(opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, opponent); + } +} diff --git a/test/battle/move_effect_secondary/thrash.c b/test/battle/move_effect_secondary/thrash.c index e7f573d1ed..4a6daf6878 100644 --- a/test/battle/move_effect_secondary/thrash.c +++ b/test/battle/move_effect_secondary/thrash.c @@ -74,7 +74,7 @@ SINGLE_BATTLE_TEST("Thrash does not confuse the user if it is canceled on turn 2 SINGLE_BATTLE_TEST("Thrash confuses the user if it is canceled on turn 3 of 3, Protect") { GIVEN { - ASSUME(B_RAMPAGE_CANCELLING >= GEN_5); + WITH_CONFIG(B_RAMPAGE_CONFUSION, GEN_5); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -89,7 +89,7 @@ SINGLE_BATTLE_TEST("Thrash confuses the user if it is canceled on turn 3 of 3, P SINGLE_BATTLE_TEST("Thrash confuses the user if it is canceled on turn 3 of 3, Immunity") { GIVEN { - ASSUME(B_RAMPAGE_CANCELLING >= GEN_5); + WITH_CONFIG(B_RAMPAGE_CONFUSION, GEN_5); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_GENGAR); @@ -117,3 +117,24 @@ SINGLE_BATTLE_TEST("Petal Dance does not lock mons that copy the move with Dance EXPECT(!(opponent->volatiles.multipleTurns)); } } + +SINGLE_BATTLE_TEST("Thrash confuses the user after it finishes even if move failed") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MovesWithPP({MOVE_THRASH, 10}); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GASTLY); + } WHEN { + TURN { MOVE(player, MOVE_THRASH); } + TURN { SKIP_TURN(player); } + TURN { SWITCH(opponent, 1); SKIP_TURN(player); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player); + } THEN { + // Check that PP has been consumed correctly + EXPECT_EQ(player->pp[0], 9); + } +}