From db7e45f1d61644bb14317cf14d941d565bf07f12 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sun, 20 Apr 2025 23:07:46 +0200 Subject: [PATCH] Sky Drop clean up and tests (#6218) Co-authored-by: Bassoonian --- include/battle_util.h | 15 ++- src/battle_end_turn.c | 8 +- src/battle_main.c | 10 +- src/battle_script_commands.c | 46 ++++----- src/battle_util.c | 159 ++++++++++++++--------------- test/battle/move_effect/sky_drop.c | 124 ++++++++++++++++++++++ 6 files changed, 249 insertions(+), 113 deletions(-) create mode 100644 test/battle/move_effect/sky_drop.c diff --git a/include/battle_util.h b/include/battle_util.h index 693a6ce8c3..43d55ef2e8 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -172,6 +172,19 @@ enum SleepClauseBlock BLOCKED_BY_SLEEP_CLAUSE, }; +enum SkyDropState +{ + SKY_DROP_IGNORE, + SKY_DROP_ATTACKCANCELLER_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 + void HandleAction_ThrowBall(void); u32 GetCurrentBattleWeather(void); bool32 EndOrContinueWeather(void); @@ -195,7 +208,7 @@ u8 GetBattlerForBattleScript(u8 caseId); bool32 IsBattlerMarkedForControllerExec(u32 battler); void MarkBattlerForControllerExec(u32 battler); void MarkBattlerReceivedLinkData(u32 battler); -const u8* CancelMultiTurnMoves(u32 battler); +const u8 *CancelMultiTurnMoves(u32 battler, enum SkyDropState skyDropState); bool32 WasUnableToUseMove(u32 battler); void PrepareStringBattle(enum StringID stringId, u32 battler); void ResetSentPokesToOpponentValue(void); diff --git a/src/battle_end_turn.c b/src/battle_end_turn.c index a511b6698d..8bee265cfe 100644 --- a/src/battle_end_turn.c +++ b/src/battle_end_turn.c @@ -445,7 +445,7 @@ static bool32 HandleEndTurnFirstEventBlock(u32 battler) gBattleMons[battler].status2 -= STATUS2_LOCK_CONFUSE_TURN(1); if (WasUnableToUseMove(battler)) { - CancelMultiTurnMoves(battler); + CancelMultiTurnMoves(battler, SKY_DROP_IGNORE); } else if (!(gBattleMons[battler].status2 & STATUS2_LOCK_CONFUSE) && (gBattleMons[battler].status2 & STATUS2_MULTIPLETURNS)) { @@ -993,7 +993,7 @@ static bool32 HandleEndTurnYawn(u32 battler) && !UproarWakeUpCheck(battler) && !IsLeafGuardProtected(battler, ability)) { - CancelMultiTurnMoves(battler); + CancelMultiTurnMoves(battler, SKY_DROP_STATUS_YAWN); gEffectBattler = gBattlerTarget = battler; if (IsBattlerTerrainAffected(battler, STATUS_FIELD_ELECTRIC_TERRAIN)) { @@ -1379,7 +1379,7 @@ static bool32 HandleEndTurnThirdEventBlock(u32 battler) gBattleMons[battler].status2 -= STATUS2_UPROAR_TURN(1); // uproar timer goes down if (WasUnableToUseMove(battler)) { - CancelMultiTurnMoves(battler); + CancelMultiTurnMoves(battler, SKY_DROP_IGNORE); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_UPROAR_ENDS; } else if (gBattleMons[battler].status2 & STATUS2_UPROAR) @@ -1390,7 +1390,7 @@ static bool32 HandleEndTurnThirdEventBlock(u32 battler) else { gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_UPROAR_ENDS; - CancelMultiTurnMoves(battler); + CancelMultiTurnMoves(battler, SKY_DROP_IGNORE); } BattleScriptExecute(BattleScript_PrintUproarOverTurns); effect = TRUE; diff --git a/src/battle_main.c b/src/battle_main.c index 2d3abd2470..b8851bdc12 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3007,7 +3007,7 @@ 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] = 0xFF; + gBattleStruct->skyDropTargets[i] = SKY_DROP_NO_TARGET; } gLastUsedMove = 0; @@ -3357,14 +3357,14 @@ const u8* FaintClearSetData(u32 battler) TryBattleFormChange(battler, FORM_CHANGE_FAINT); // If the fainted mon was involved in a Sky Drop - if (gBattleStruct->skyDropTargets[battler] != 0xFF) + 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] = 0xFF; - gBattleStruct->skyDropTargets[otherSkyDropper] = 0xFF; + 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 (gStatuses3[otherSkyDropper] & STATUS3_SKY_DROPPED) @@ -3915,7 +3915,7 @@ static void HandleEndTurn_ContinueBattle(void) { gBattleMons[i].status2 &= ~STATUS2_FLINCHED; if ((gBattleMons[i].status1 & STATUS1_SLEEP) && (gBattleMons[i].status2 & STATUS2_MULTIPLETURNS)) - CancelMultiTurnMoves(i); + CancelMultiTurnMoves(i, SKY_DROP_IGNORE); } gBattleStruct->eventBlockCounter = 0; gBattleStruct->turnEffectsBattlerId = 0; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index b38ab24741..b46a412011 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1319,7 +1319,7 @@ static void Cmd_attackcanceler(void) gBattlescriptCurrInstr = BattleScript_FailedFromAtkString; if (!gBattleMoveEffects[effect].twoTurnEffect || (gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS)) - CancelMultiTurnMoves(gBattlerAttacker); + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); return; } @@ -1423,7 +1423,7 @@ static void Cmd_attackcanceler(void) { if (IsMoveMakingContact(gCurrentMove, gBattlerAttacker)) gProtectStructs[gBattlerAttacker].touchedProtectLike = TRUE; - CancelMultiTurnMoves(gBattlerAttacker); + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; gLastLandedMoves[gBattlerTarget] = 0; gLastHitByType[gBattlerTarget] = 0; @@ -1504,14 +1504,14 @@ static bool32 AccuracyCalcHelper(u32 move, u32 battler) // 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 (GetBattlerAbility(gBattlerAttacker) == ABILITY_NO_GUARD && !(gStatuses3[battler] & STATUS3_COMMANDER) - && (moveEffect != EFFECT_SKY_DROP || gBattleStruct->skyDropTargets[battler] == 0xFF)) + && (moveEffect != EFFECT_SKY_DROP || gBattleStruct->skyDropTargets[battler] == SKY_DROP_NO_TARGET)) { 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 (GetBattlerAbility(battler) == ABILITY_NO_GUARD - && (moveEffect != EFFECT_SKY_DROP || gBattleStruct->skyDropTargets[battler] == 0xFF)) + && (moveEffect != EFFECT_SKY_DROP || gBattleStruct->skyDropTargets[battler] == SKY_DROP_NO_TARGET)) { effect = TRUE; ability = ABILITY_NO_GUARD; @@ -3303,7 +3303,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) if (!CanBeSlept(gEffectBattler, GetBattlerAbility(gEffectBattler), BLOCKED_BY_SLEEP_CLAUSE) && !gBattleStruct->battlerState[gEffectBattler].sleepClauseEffectExempt) break; - cancelMultiTurnMovesResult = CancelMultiTurnMoves(gEffectBattler); + cancelMultiTurnMovesResult = CancelMultiTurnMoves(gEffectBattler, SKY_DROP_STATUS_FREEZE_SLEEP); if (cancelMultiTurnMovesResult) gBattlescriptCurrInstr = cancelMultiTurnMovesResult; statusChanged = TRUE; @@ -3397,7 +3397,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) if (!CanBeFrozen(gEffectBattler)) break; - cancelMultiTurnMovesResult = CancelMultiTurnMoves(gEffectBattler); + cancelMultiTurnMovesResult = CancelMultiTurnMoves(gEffectBattler, SKY_DROP_STATUS_FREEZE_SLEEP); if (cancelMultiTurnMovesResult) gBattlescriptCurrInstr = cancelMultiTurnMovesResult; statusChanged = TRUE; @@ -4320,8 +4320,8 @@ void SetMoveEffect(bool32 primary, bool32 certain) MarkBattlerForControllerExec(gBattlerTarget); } - if (gBattleMons[gBattlerTarget].pp[i] == 0 && gBattleStruct->skyDropTargets[gBattlerTarget] == 0xFF) - CancelMultiTurnMoves(gBattlerTarget); + if (gBattleMons[gBattlerTarget].pp[i] == 0 && gBattleStruct->skyDropTargets[gBattlerTarget] == SKY_DROP_NO_TARGET) + CancelMultiTurnMoves(gBattlerTarget, SKY_DROP_IGNORE); BattleScriptPush(gBattlescriptCurrInstr + 1); gBattlescriptCurrInstr = BattleScript_MoveEffectEerieSpell; @@ -6986,7 +6986,7 @@ static void Cmd_moveend(void) case MOVEEND_SKY_DROP_CONFUSE: // If a Pokemon was released from Sky Drop and was in LOCK_CONFUSE, go to "confused due to fatigue" scripts and clear Sky Drop data. for (i = 0; i < gBattlersCount; i++) { - if (gBattleStruct->skyDropTargets[i] == 0xFE) + if (gBattleStruct->skyDropTargets[i] == SKY_DROP_RELEASED_TARGET) { u8 targetId; // Find the battler id of the Pokemon that was held by Sky Drop @@ -7003,8 +7003,8 @@ static void Cmd_moveend(void) gBattlescriptCurrInstr = BattleScript_ThrashConfuses; // Clear skyDropTargets data - gBattleStruct->skyDropTargets[i] = 0xFF; - gBattleStruct->skyDropTargets[targetId] = 0xFF; + gBattleStruct->skyDropTargets[i] = SKY_DROP_NO_TARGET; + gBattleStruct->skyDropTargets[targetId] = SKY_DROP_NO_TARGET; return; } } @@ -7544,7 +7544,7 @@ static void Cmd_moveend(void) && MoveHasAdditionalEffectSelf(gCurrentMove, MOVE_EFFECT_THRASH) // If we're rampaging && gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT // And it is unusable && (gBattleMons[gBattlerAttacker].status2 & STATUS2_LOCK_CONFUSE) != STATUS2_LOCK_CONFUSE_TURN(1)) // And won't end this turn - CancelMultiTurnMoves(gBattlerAttacker); // Cancel it + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_IGNORE); // Cancel it if (gBattleStruct->savedAttackerCount > 0) { @@ -10442,7 +10442,7 @@ static void Cmd_various(void) VARIOUS_ARGS(); // Cancel all multiturn moves of IN_AIR Pokemon except those being targeted by Sky Drop. if (gStatuses3[battler] & STATUS3_ON_AIR && !(gStatuses3[battler] & STATUS3_SKY_DROPPED)) - CancelMultiTurnMoves(battler); + CancelMultiTurnMoves(battler, SKY_DROP_GRAVITY_ON_AIRBORNE); gStatuses3[battler] &= ~(STATUS3_MAGNET_RISE | STATUS3_TELEKINESIS | STATUS3_ON_AIR | STATUS3_SKY_DROPPED); break; @@ -10483,7 +10483,7 @@ static void Cmd_various(void) { VARIOUS_ARGS(); const u8 *result; - result = CancelMultiTurnMoves(battler); + result = CancelMultiTurnMoves(battler, SKY_DROP_CANCEL_MULTI_TURN_MOVES); if (result) { gBattlescriptCurrInstr = result; @@ -11505,12 +11505,12 @@ static void Cmd_various(void) VARIOUS_ARGS(const u8 *failInstr); // Check to see if the initial target of this Sky Drop fainted before the 2nd turn of Sky Drop. // If so, make the move fail. If not, clear all of the statuses and continue the move. - if (gBattleStruct->skyDropTargets[gBattlerAttacker] == 0xFF) + if (gBattleStruct->skyDropTargets[gBattlerAttacker] == SKY_DROP_NO_TARGET) gBattlescriptCurrInstr = cmd->failInstr; else { - gBattleStruct->skyDropTargets[gBattlerAttacker] = 0xFF; - gBattleStruct->skyDropTargets[gBattlerTarget] = 0xFF; + gBattleStruct->skyDropTargets[gBattlerAttacker] = SKY_DROP_NO_TARGET; + gBattleStruct->skyDropTargets[gBattlerTarget] = SKY_DROP_NO_TARGET; gStatuses3[gBattlerTarget] &= ~(STATUS3_SKY_DROPPED | STATUS3_ON_AIR); gBattlescriptCurrInstr = cmd->nextInstr; } @@ -11523,14 +11523,14 @@ static void Cmd_various(void) case VARIOUS_SKY_DROP_YAWN: // If the mon that's sleeping due to Yawn was holding a Pokemon in Sky Drop, release the target and clear Sky Drop data. { VARIOUS_ARGS(); - if (gBattleStruct->skyDropTargets[gEffectBattler] != 0xFF && !(gStatuses3[gEffectBattler] & STATUS3_SKY_DROPPED)) + if (gBattleStruct->skyDropTargets[gEffectBattler] != SKY_DROP_NO_TARGET && !(gStatuses3[gEffectBattler] & STATUS3_SKY_DROPPED)) { // Set the target of Sky Drop as gEffectBattler gEffectBattler = gBattleStruct->skyDropTargets[gEffectBattler]; // Clear skyDropTargets data - gBattleStruct->skyDropTargets[gBattleStruct->skyDropTargets[gEffectBattler]] = 0xFF; - gBattleStruct->skyDropTargets[gEffectBattler] = 0xFF; + 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].status2 & STATUS2_LOCK_CONFUSE && CanBeConfused(gEffectBattler)) @@ -14046,8 +14046,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] == 0xFF) - CancelMultiTurnMoves(gBattlerTarget); + if (gBattleMons[gBattlerTarget].pp[i] == 0 && gBattleStruct->skyDropTargets[gBattlerTarget] == SKY_DROP_NO_TARGET) + CancelMultiTurnMoves(gBattlerTarget, SKY_DROP_IGNORE); } else { @@ -14237,7 +14237,7 @@ static void Cmd_handlerollout(void) if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) { - CancelMultiTurnMoves(gBattlerAttacker); + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_IGNORE); gBattlescriptCurrInstr = BattleScript_MoveMissedPause; } else diff --git a/src/battle_util.c b/src/battle_util.c index 23050d7298..cd2f29d603 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -1024,7 +1024,68 @@ void MarkBattlerReceivedLinkData(u32 battler) gBattleControllerExecFlags &= ~(1u << (28 + battler)); } -const u8* CancelMultiTurnMoves(u32 battler) +const u8 *CheckSkyDropState(u32 battler, enum SkyDropState skyDropState) +{ + const u8 *result = NULL; + + u8 otherSkyDropper = gBattleStruct->skyDropTargets[battler]; + gStatuses3[otherSkyDropper] &= ~(STATUS3_SKY_DROPPED | STATUS3_ON_AIR); + + // 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].status2 & STATUS2_LOCK_CONFUSE && skyDropState != SKY_DROP_STATUS_YAWN) + { + gBattleMons[otherSkyDropper].status2 &= ~(STATUS2_LOCK_CONFUSE); + + // If the target can be confused, confuse them. + // Don't use CanBeConfused, can cause issues in edge cases. + if (!(GetBattlerAbility(otherSkyDropper) == ABILITY_OWN_TEMPO + || gBattleMons[otherSkyDropper].status2 & STATUS2_CONFUSION + || IsBattlerTerrainAffected(otherSkyDropper, STATUS_FIELD_MISTY_TERRAIN))) + { + // Set confused status + gBattleMons[otherSkyDropper].status2 |= STATUS2_CONFUSION_TURN(((Random()) % 4) + 2); + + if (skyDropState == SKY_DROP_ATTACKCANCELLER_CHECK) + { + gBattleStruct->skyDropTargets[battler] = SKY_DROP_RELEASED_TARGET; + } + else if (skyDropState == SKY_DROP_GRAVITY_ON_AIRBORNE) + { + // Reapplying STATUS3_SKY_DROPPED allows for avoiding unecessary messages when Gravity is applied to the target. + gBattleStruct->skyDropTargets[battler] = SKY_DROP_RELEASED_TARGET; + gStatuses3[otherSkyDropper] |= STATUS3_SKY_DROPPED; + } + 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].status2 & STATUS2_LOCK_CONFUSE) && gBattleStruct->skyDropTargets[battler] < 4) + { + gBattleStruct->skyDropTargets[battler] = SKY_DROP_NO_TARGET; + gBattleStruct->skyDropTargets[otherSkyDropper] = SKY_DROP_NO_TARGET; + } + + return result; +} + +const u8 *CancelMultiTurnMoves(u32 battler, enum SkyDropState skyDropState) { const u8 *result = NULL; gBattleMons[battler].status2 &= ~(STATUS2_UPROAR); @@ -1045,70 +1106,8 @@ const u8* CancelMultiTurnMoves(u32 battler) if (!(gStatuses3[battler] & STATUS3_SKY_DROPPED)) gStatuses3[battler] &= ~(STATUS3_SEMI_INVULNERABLE); - // Check to see if this Pokemon was in the middle of using Sky Drop. If so, release the target. - if (gBattleStruct->skyDropTargets[battler] != 0xFF && !(gStatuses3[battler] & STATUS3_SKY_DROPPED)) - { - // Get the target's battler id - u8 otherSkyDropper = gBattleStruct->skyDropTargets[battler]; - - // Clears sky_dropped and on_air statuses - gStatuses3[otherSkyDropper] &= ~(STATUS3_SKY_DROPPED | STATUS3_ON_AIR); - - // 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].status2 & STATUS2_LOCK_CONFUSE && gBattleStruct->eventBlockCounter != 24) - { - gBattleMons[otherSkyDropper].status2 &= ~(STATUS2_LOCK_CONFUSE); - - // If the target can be confused, confuse them. - // Don't use CanBeConfused, can cause issues in edge cases. - if (!(GetBattlerAbility(otherSkyDropper) == ABILITY_OWN_TEMPO - || gBattleMons[otherSkyDropper].status2 & STATUS2_CONFUSION - || IsBattlerTerrainAffected(otherSkyDropper, STATUS_FIELD_MISTY_TERRAIN))) - { - // Set confused status - gBattleMons[otherSkyDropper].status2 |= STATUS2_CONFUSION_TURN(((Random()) % 4) + 2); - - // If this CancelMultiTurnMoves is occuring due to attackcanceller - if (gBattlescriptCurrInstr[0] == 0x0) - { - gBattleStruct->skyDropTargets[battler] = 0xFE; - } - // If this CancelMultiTurnMoves is occuring due to VARIOUS_GRAVITY_ON_AIRBORNE_MONS - // Reapplying STATUS3_SKY_DROPPED allows for avoiding unecessary messages when Gravity is applied to the target. - else if (gBattlescriptCurrInstr[0] == 0x76 && gBattlescriptCurrInstr[2] == 76) - { - gBattleStruct->skyDropTargets[battler] = 0xFE; - gStatuses3[otherSkyDropper] |= STATUS3_SKY_DROPPED; - } - // If this CancelMultiTurnMoves is occuring due to cancelmultiturnmoves script - else if (gBattlescriptCurrInstr[0] == 0x76 && gBattlescriptCurrInstr[2] == 0) - { - gBattlerAttacker = otherSkyDropper; - result = BattleScript_ThrashConfuses; - } - // If this CancelMultiTurnMoves is occuring due to receiving Sleep/Freeze status - else if (gBattleScripting.moveEffect <= PRIMARY_STATUS_MOVE_EFFECT) - { - 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].status2 & STATUS2_LOCK_CONFUSE) && gBattleStruct->skyDropTargets[battler] < 4) - { - gBattleStruct->skyDropTargets[battler] = 0xFF; - gBattleStruct->skyDropTargets[otherSkyDropper] = 0xFF; - } - } + if (gBattleStruct->skyDropTargets[battler] != SKY_DROP_NO_TARGET && !(gStatuses3[battler] & STATUS3_SKY_DROPPED)) + result = CheckSkyDropState(battler, skyDropState); gDisableStructs[battler].rolloutTimer = 0; gDisableStructs[battler].furyCutterCounter = 0; @@ -1303,7 +1302,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && move == gLastMoves[battler] && move != MOVE_STRUGGLE && (gBattleMons[battler].status2 & STATUS2_TORMENT)) { - CancelMultiTurnMoves(battler); + CancelMultiTurnMoves(battler, SKY_DROP_IGNORE); if (gBattleTypeFlags & BATTLE_TYPE_PALACE) { gPalaceSelectionBattleScripts[battler] = BattleScript_SelectingTormentedMoveInPalace; @@ -1672,7 +1671,7 @@ bool32 BattleArenaTurnEnd(void) && IsBattlerAlive(B_POSITION_PLAYER_LEFT) && IsBattlerAlive(B_POSITION_OPPONENT_LEFT)) { for (u32 battler = 0; battler < 2; battler++) - CancelMultiTurnMoves(battler); + CancelMultiTurnMoves(battler, SKY_DROP_IGNORE); gBattlescriptCurrInstr = BattleScript_ArenaDoJudgment; BattleScriptExecute(BattleScript_ArenaDoJudgment); @@ -1875,7 +1874,7 @@ static void CancellerRecharge(u32 *effect) { gBattleMons[gBattlerAttacker].status2 &= ~STATUS2_RECHARGE; gDisableStructs[gBattlerAttacker].rechargeTimer = 0; - CancelMultiTurnMoves(gBattlerAttacker); + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); gBattlescriptCurrInstr = BattleScript_MoveUsedMustRecharge; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; *effect = 1; @@ -2011,7 +2010,7 @@ static void CancellerTruant(u32 *effect) { if (GetBattlerAbility(gBattlerAttacker) == ABILITY_TRUANT && gDisableStructs[gBattlerAttacker].truantCounter) { - CancelMultiTurnMoves(gBattlerAttacker); + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_LOAFING; gBattlerAbility = gBattlerAttacker; @@ -2026,7 +2025,7 @@ static void CancellerFlinch(u32 *effect) if (gBattleMons[gBattlerAttacker].status2 & STATUS2_FLINCHED) { gProtectStructs[gBattlerAttacker].flinchImmobility = TRUE; - CancelMultiTurnMoves(gBattlerAttacker); + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); gBattlescriptCurrInstr = BattleScript_MoveUsedFlinched; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; *effect = 1; @@ -2047,7 +2046,7 @@ static void CancellerInLove(u32 *effect) BattleScriptPush(BattleScript_MoveUsedIsInLoveCantAttack); gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; gProtectStructs[gBattlerAttacker].loveImmobility = TRUE; - CancelMultiTurnMoves(gBattlerAttacker); + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); } gBattlescriptCurrInstr = BattleScript_MoveUsedIsInLove; *effect = 1; @@ -2060,7 +2059,7 @@ static void CancellerDisabled(u32 *effect) { gProtectStructs[gBattlerAttacker].usedDisabledMove = TRUE; gBattleScripting.battler = gBattlerAttacker; - CancelMultiTurnMoves(gBattlerAttacker); + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); gBattlescriptCurrInstr = BattleScript_MoveUsedIsDisabled; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; *effect = 1; @@ -2073,7 +2072,7 @@ static void CancellerHealBlocked(u32 *effect) { gProtectStructs[gBattlerAttacker].usedHealBlockedMove = TRUE; gBattleScripting.battler = gBattlerAttacker; - CancelMultiTurnMoves(gBattlerAttacker); + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); gBattlescriptCurrInstr = BattleScript_MoveUsedHealBlockPrevents; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; *effect = 1; @@ -2086,7 +2085,7 @@ static void CancellerGravity(u32 *effect) { gProtectStructs[gBattlerAttacker].usedGravityPreventedMove = TRUE; gBattleScripting.battler = gBattlerAttacker; - CancelMultiTurnMoves(gBattlerAttacker); + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); gBattlescriptCurrInstr = BattleScript_MoveUsedGravityPrevents; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; *effect = 1; @@ -2098,7 +2097,7 @@ static void CancellerThroatChop(u32 *effect) if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[gBattlerAttacker].throatChopTimer > gBattleTurnCounter && IsSoundMove(gCurrentMove)) { gProtectStructs[gBattlerAttacker].usedThroatChopPreventedMove = TRUE; - CancelMultiTurnMoves(gBattlerAttacker); + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); gBattlescriptCurrInstr = BattleScript_MoveUsedIsThroatChopPrevented; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; *effect = 1; @@ -2110,7 +2109,7 @@ static void CancellerTaunted(u32 *effect) if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[gBattlerAttacker].tauntTimer && IsBattleMoveStatus(gCurrentMove)) { gProtectStructs[gBattlerAttacker].usedTauntedMove = TRUE; - CancelMultiTurnMoves(gBattlerAttacker); + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); gBattlescriptCurrInstr = BattleScript_MoveUsedIsTaunted; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; *effect = 1; @@ -2122,7 +2121,7 @@ static void CancellerImprisoned(u32 *effect) if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && GetImprisonedMovesCount(gBattlerAttacker, gCurrentMove)) { gProtectStructs[gBattlerAttacker].usedImprisonedMove = TRUE; - CancelMultiTurnMoves(gBattlerAttacker); + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); gBattlescriptCurrInstr = BattleScript_MoveUsedIsImprisoned; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; *effect = 1; @@ -2181,7 +2180,7 @@ static void CancellerParalysed(u32 *effect) { gProtectStructs[gBattlerAttacker].prlzImmobility = TRUE; // This is removed in FRLG and Emerald for some reason - //CancelMultiTurnMoves(gBattlerAttacker); + //CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); gBattlescriptCurrInstr = BattleScript_MoveUsedIsParalyzed; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; *effect = 1; @@ -2327,7 +2326,7 @@ static void CancellerPsychicTerrain(u32 *effect) && GetMoveTarget(gCurrentMove) != MOVE_TARGET_OPPONENTS_FIELD && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget)) { - CancelMultiTurnMoves(gBattlerAttacker); + CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); gBattlescriptCurrInstr = BattleScript_MoveUsedPsychicTerrainPrevents; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; *effect = 1; @@ -2972,7 +2971,7 @@ bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 abilityAtk, u32 a && !(IsBattleMoveStatus(move) && (abilityDef == ABILITY_MAGIC_BOUNCE || gProtectStructs[battlerDef].bounceMove))) { if (option == ABILITY_RUN_SCRIPT && !IsSpreadMove(GetBattlerMoveTargetType(battlerAtk, move))) - CancelMultiTurnMoves(battlerAtk); // Don't cancel moves that can hit two targets bc one target might not be protected + CancelMultiTurnMoves(battlerAtk, SKY_DROP_ATTACKCANCELLER_CHECK); // Don't cancel moves that can hit two targets bc one target might not be protected battleScriptBlocksMove = BattleScript_DarkTypePreventsPrankster; } diff --git a/test/battle/move_effect/sky_drop.c b/test/battle/move_effect/sky_drop.c new file mode 100644 index 0000000000..c2a1e104e1 --- /dev/null +++ b/test/battle/move_effect/sky_drop.c @@ -0,0 +1,124 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_SKY_DROP) == EFFECT_SKY_DROP); +} + +SINGLE_BATTLE_TEST("Sky Drop does no damage to Flying type Pokémon") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_PIDGEY].weight < 2000); + ASSUME(gSpeciesInfo[SPECIES_PIDGEY].types[1] == TYPE_FLYING); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY); + } WHEN { + TURN { MOVE(player, MOVE_SKY_DROP); } + TURN { SKIP_TURN(player); } + } SCENE { + MESSAGE("Wobbuffet took the opposing Pidgey into the sky!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, player); + NOT HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Sky Drop fails if target is behind a substitute") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_SKY_DROP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, player); + } +} + +SINGLE_BATTLE_TEST("Sky Drop fails if target is in a Semi-Invulnerable state") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_FLY); MOVE(player, MOVE_SKY_DROP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLY, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, player); + } +} + +DOUBLE_BATTLE_TEST("Sky Drop is cancelled if Gravity activated") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_SKY_DROP, target: opponentLeft); + MOVE(playerRight, MOVE_GRAVITY); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRAVITY, playerRight); + MESSAGE("Wobbuffet fell from the sky due to the gravity!"); + } +} + +SINGLE_BATTLE_TEST("Sky Drop fails on heavy targets") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_METAGROSS].weight >= 2000); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_METAGROSS); + } WHEN { + TURN { MOVE(player, MOVE_SKY_DROP); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, player); + MESSAGE("The opposing Metagross is too heavy to be lifted!"); + } +} + +SINGLE_BATTLE_TEST("Sky Drop cancels targets two turn moves") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponent, MOVE_SOLAR_BEAM); + MOVE(player, MOVE_SKY_DROP); + } + TURN { SKIP_TURN(player); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SOLAR_BEAM, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SOLAR_BEAM, opponent); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Sky Drop stops the confusion count until the target is dropped") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_THRASH); } + TURN { SKIP_TURN(player); } + TURN { SKIP_TURN(player); } + TURN { MOVE(opponent, MOVE_SKY_DROP); } + TURN { SKIP_TURN(opponent); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player); + } +}