From 6fe5f6530ab4af2b373154d5282b2187c93d0aa4 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Wed, 7 May 2025 00:30:43 +0200 Subject: [PATCH] General MoveEnd clean up and Fell Stinger adjustments (#6674) --- asm/macros/battle_script.inc | 4 - data/battle_scripts_1.s | 1 - include/constants/battle_script_commands.h | 10 +- include/constants/generational_changes.h | 1 + include/generational_changes.h | 1 + src/battle_script_commands.c | 365 ++++++++++----------- test/battle/move_effect/fell_stinger.c | 43 +++ 7 files changed, 216 insertions(+), 209 deletions(-) create mode 100644 test/battle/move_effect/fell_stinger.c diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 038056248c..996ed24d3b 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1954,10 +1954,6 @@ various BS_ATTACKER, VARIOUS_TRY_ACTIVATE_SOULHEART .endm - .macro tryactivatefellstinger battler:req - various \battler, VARIOUS_TRY_ACTIVATE_FELL_STINGER - .endm - .macro playmoveanimation battler:req, move:req various \battler, VARIOUS_PLAY_MOVE_ANIMATION .2byte \move diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 5754fa9915..9348ba5a80 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -5233,7 +5233,6 @@ BattleScript_FaintTarget:: dofaintanimation BS_TARGET printstring STRINGID_TARGETFAINTED cleareffectsonfaint BS_TARGET - tryactivatefellstinger BS_ATTACKER tryactivatesoulheart tryactivatereceiver BS_TARGET trytrainerslidefirstdownmsg BS_TARGET diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 81cc24f910..051a49b076 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -121,7 +121,6 @@ enum CmdVarious VARIOUS_INSTANT_HP_DROP, VARIOUS_CLEAR_STATUS, VARIOUS_RESTORE_PP, - VARIOUS_TRY_ACTIVATE_FELL_STINGER, VARIOUS_PLAY_MOVE_ANIMATION, VARIOUS_SET_LUCKY_CHANT, VARIOUS_SUCKER_PUNCH_CHECK, @@ -262,7 +261,7 @@ enum MoveEndEffects MOVEEND_ATTACKER_VISIBLE, MOVEEND_TARGET_VISIBLE, MOVEEND_ITEM_EFFECTS_TARGET, - MOVEEND_MOVE_EFFECTS2, + MOVEEND_FIRST_MOVE_BLOCK, MOVEEND_ITEM_EFFECTS_ALL, MOVEEND_SYMBIOSIS, MOVEEND_HIT_SWITCH_TARGET, @@ -271,14 +270,13 @@ enum MoveEndEffects 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 multi-hit move + MOVEEND_NEXT_TARGET, // Everything up until here is handled for each strike of a spread move MOVEEND_MULTIHIT_MOVE, MOVEEND_DEFROST, - MOVEEND_RECOIL, - MOVEEND_RAPID_SPIN, + MOVEEND_SECOND_MOVE_BLOCK, MOVEEND_ITEM_EFFECTS_ATTACKER, MOVEEND_ABILITY_BLOCK, - MOVEEND_SHEER_FORCE, // If move is Sheer Force affected, skip until Eject Pack + MOVEEND_SHEER_FORCE, // If move is Sheer Force affected, skip until Opportunist MOVEEND_RED_CARD, // Red Card triggers before Eject Pack MOVEEND_EJECT_BUTTON, MOVEEND_LIFEORB_SHELLBELL, // Includes shell bell, throat spray, etc diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 47ad67c4c6..180d0224d7 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -15,6 +15,7 @@ enum GenConfigTag GEN_CONFIG_MOODY_STATS, GEN_CONFIG_BATTLE_BOND, GEN_CONFIG_ATE_MULTIPLIER, + GEN_CONFIG_FELL_STINGER_STAT_RAISE, GEN_CONFIG_COUNT }; diff --git a/include/generational_changes.h b/include/generational_changes.h index f4ab55a08b..93a5aaa1ae 100644 --- a/include/generational_changes.h +++ b/include/generational_changes.h @@ -17,6 +17,7 @@ static const u8 sGenerationalChanges[GEN_CONFIG_COUNT] = [GEN_CONFIG_TELEPORT_BEHAVIOR] = B_TELEPORT_BEHAVIOR, [GEN_CONFIG_MOODY_STATS] = B_MOODY_ACC_EVASION, [GEN_CONFIG_BATTLE_BOND] = B_BATTLE_BOND, + [GEN_CONFIG_FELL_STINGER_STAT_RAISE] = B_FELL_STINGER_STAT_RAISE, [GEN_CONFIG_ATE_MULTIPLIER] = B_ATE_MULTIPLIER, }; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 85fe7a3d11..60099c6904 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1540,8 +1540,18 @@ static bool32 AccuracyCalcHelper(u32 move, u32 battler) gBattleStruct->moveResultFlags[battler] |= MOVE_RESULT_MISSED; effect = TRUE; } + else if (B_MINIMIZE_DMG_ACC >= GEN_6 + && (gStatuses3[battler] & STATUS3_MINIMIZED) + && MoveIncreasesPowerToMinimizedTargets(move)) + { + effect = TRUE; + } + else if (GetMoveAccuracy(move) == 0) + { + effect = TRUE; + } - if (HasWeatherEffect()) + if (!effect && HasWeatherEffect()) { if ((moveEffect == EFFECT_THUNDER || moveEffect == EFFECT_RAIN_ALWAYS_HIT) && IsBattlerWeatherAffected(battler, B_WEATHER_RAIN)) @@ -1553,17 +1563,6 @@ static bool32 AccuracyCalcHelper(u32 move, u32 battler) return effect; } - if (B_MINIMIZE_DMG_ACC >= GEN_6 - && (gStatuses3[battler] & STATUS3_MINIMIZED) - && MoveIncreasesPowerToMinimizedTargets(move)) - { - effect = TRUE; - } - else if (GetMoveAccuracy(move) == 0) - { - effect = TRUE; - } - if (ability != ABILITY_NONE) RecordAbilityBattle(gBattlerAttacker, ABILITY_NO_GUARD); @@ -6210,7 +6209,6 @@ static bool32 HandleMoveEndAbilityBlock(u32 battlerAtk, u32 battlerDef, u32 move if (gBattleStruct->battleBondBoost[side] & (1u << gBattlerPartyIndexes[battlerAtk])) break; - if (GetGenConfig(GEN_CONFIG_BATTLE_BOND) < GEN_9 && gBattleMons[battlerAtk].species == SPECIES_GRENINJA_BATTLE_BOND) { // TODO: Convert this to a proper FORM_CHANGE type. @@ -6381,13 +6379,7 @@ static void Cmd_moveend(void) effect = 1; } break; - case PROTECT_NONE: - case PROTECT_NORMAL: - case PROTECT_MAX_GUARD: - case PROTECT_WIDE_GUARD: - case PROTECT_QUICK_GUARD: - case PROTECT_CRAFTY_SHIELD: - case PROTECT_MAT_BLOCK: + default: break; } @@ -6410,33 +6402,27 @@ static void Cmd_moveend(void) case MOVEEND_ABSORB: if (moveEffect == EFFECT_ABSORB && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) + && !(gStatuses3[gBattlerAttacker] & STATUS3_HEAL_BLOCK) + && IsBattlerAlive(gBattlerAttacker) && IsBattlerTurnDamaged(gBattlerTarget)) { - if (gStatuses3[gBattlerAttacker] & STATUS3_HEAL_BLOCK && IsHealingMove(gCurrentMove)) + gBattleStruct->moveDamage[gBattlerAttacker] = max(1, (gBattleStruct->moveDamage[gBattlerTarget] * GetMoveAbsorbPercentage(gCurrentMove) / 100)); + gBattleStruct->moveDamage[gBattlerAttacker] = GetDrainedBigRootHp(gBattlerAttacker, gBattleStruct->moveDamage[gBattlerAttacker]); + gHitMarker |= HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_IGNORE_DISGUISE; + effect = TRUE; + if (GetBattlerAbility(gBattlerTarget) == ABILITY_LIQUID_OOZE) { - gBattleScripting.moveendState++; - break; + gBattleStruct->moveDamage[gBattlerAttacker] *= -1; + gHitMarker |= HITMARKER_PASSIVE_DAMAGE; + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ABSORB_OOZE; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_EffectAbsorbLiquidOoze; } - else if (IsBattlerAlive(gBattlerAttacker) && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) + else { - gBattleStruct->moveDamage[gBattlerAttacker] = max(1, (gBattleStruct->moveDamage[gBattlerTarget] * GetMoveAbsorbPercentage(gCurrentMove) / 100)); - gBattleStruct->moveDamage[gBattlerAttacker] = GetDrainedBigRootHp(gBattlerAttacker, gBattleStruct->moveDamage[gBattlerAttacker]); - gHitMarker |= HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_IGNORE_DISGUISE; - effect = TRUE; - if (GetBattlerAbility(gBattlerTarget) == ABILITY_LIQUID_OOZE) - { - gBattleStruct->moveDamage[gBattlerAttacker] *= -1; - gHitMarker |= HITMARKER_PASSIVE_DAMAGE; - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ABSORB_OOZE; - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_EffectAbsorbLiquidOoze; - } - else - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ABSORB; - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_EffectAbsorb; - } + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ABSORB; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_EffectAbsorb; } } gBattleScripting.moveendState++; @@ -6458,109 +6444,6 @@ static void Cmd_moveend(void) } gBattleScripting.moveendState++; break; - case MOVEEND_DEFROST: // defrosting check - if (gBattleMons[gBattlerTarget].status1 & STATUS1_FREEZE - && IsBattlerTurnDamaged(gBattlerTarget) - && IsBattlerAlive(gBattlerTarget) - && gBattlerAttacker != gBattlerTarget - && (moveType == TYPE_FIRE || CanBurnHitThaw(gCurrentMove)) - && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) - { - gBattleMons[gBattlerTarget].status1 &= ~STATUS1_FREEZE; - BtlController_EmitSetMonData(gBattlerTarget, BUFFER_A, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1); - MarkBattlerForControllerExec(gBattlerTarget); - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_DefrostedViaFireMove; - effect = TRUE; - } - if (gBattleMons[gBattlerTarget].status1 & STATUS1_FROSTBITE - && IsBattlerAlive(gBattlerTarget) - && gBattlerAttacker != gBattlerTarget - && MoveThawsUser(originallyUsedMove) - && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) - { - gBattleMons[gBattlerTarget].status1 &= ~STATUS1_FROSTBITE; - BtlController_EmitSetMonData(gBattlerTarget, BUFFER_A, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1); - MarkBattlerForControllerExec(gBattlerTarget); - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_FrostbiteHealedViaFireMove; - effect = TRUE; - } - gBattleScripting.moveendState++; - break; - case MOVEEND_RECOIL: - { - u32 moveRecoil = GetMoveRecoil(gCurrentMove); - if (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) - { - gBattleScripting.moveendState++; - break; - } - else if (GetMoveEffect(gCurrentMove) == EFFECT_RECOIL_IF_MISS - && (!IsBattlerTurnDamaged(gBattlerTarget) || gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) - && !gBattleStruct->noTargetPresent - && IsBattlerAlive(gBattlerAttacker)) - { - if (B_RECOIL_IF_MISS_DMG >= GEN_5 || (B_CRASH_IF_TARGET_IMMUNE == GEN_4 && gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_DOESNT_AFFECT_FOE)) - gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerAttacker) / 2; - else if (B_RECOIL_IF_MISS_DMG == GEN_4 && (GetNonDynamaxMaxHP(gBattlerTarget) / 2) < gBattleStruct->moveDamage[gBattlerTarget]) - gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerTarget) / 2; - else // Fallback if B_RECOIL_IF_MISS_DMG is set to gen3 or lower. - gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerTarget) / 2; - - if (gBattleStruct->moveDamage[gBattlerAttacker] == 0) - gBattleStruct->moveDamage[gBattlerAttacker] = 1; - - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_RecoilIfMiss; - effect = TRUE; - } - else if (moveEffect == EFFECT_RECOIL - && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) - && IsBattlerAlive(gBattlerAttacker) - && IsBattlerTurnDamaged(gBattlerTarget)) - { - gBattleStruct->moveDamage[gBattlerAttacker] = max(1, gBattleStruct->moveDamage[gBattlerTarget] * max(1, moveRecoil) / 100); - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_MoveEffectRecoil; - effect = TRUE; - } - else if (moveEffect == EFFECT_EXPLOSION && !IsAbilityOnField(ABILITY_DAMP)) - { - gBattleStruct->moveDamage[gBattlerAttacker] = 0; - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_FaintAttackerForExplosion; - effect = TRUE; - } - else if ((moveEffect == EFFECT_MAX_HP_50_RECOIL || moveEffect == EFFECT_MIND_BLOWN) - && IsBattlerAlive(gBattlerAttacker) - && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FAILED) - && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD) - { - gBattleStruct->moveDamage[gBattlerAttacker] = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_MaxHp50Recoil; - effect = TRUE; - } - gBattleScripting.moveendState++; - break; - } - case MOVEEND_RAPID_SPIN: - if (GetMoveEffect(gCurrentMove) == EFFECT_RAPID_SPIN - && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) - && IsBattlerTurnDamaged(gBattlerTarget)) - { - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_RapidSpinAway; - effect = TRUE; - } - gBattleScripting.moveendState++; - break; - case MOVEEND_ITEM_EFFECTS_ATTACKER: - if (ItemBattleEffects(ITEMEFFECT_MOVE_END, gBattlerAttacker, FALSE)) - effect = TRUE; - gBattleScripting.moveendState++; - break; case MOVEEND_SYNCHRONIZE_TARGET: // target synchronize if (AbilityBattleEffects(ABILITYEFFECT_SYNCHRONIZE, gBattlerTarget, 0, 0, 0)) effect = TRUE; @@ -6576,12 +6459,6 @@ static void Cmd_moveend(void) effect = TRUE; gBattleScripting.moveendState++; break; - case MOVEEND_OPPORTUNIST: - if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0)) - effect = TRUE; // it loops through all battlers, so we increment after its done with all battlers - else - gBattleScripting.moveendState++; - break; case MOVEEND_STATUS_IMMUNITY_ABILITIES: // status immunities if (AbilityBattleEffects(ABILITYEFFECT_IMMUNITY, 0, 0, 0, 0)) effect = TRUE; // it loops through all battlers, so we increment after its done with all battlers @@ -6622,23 +6499,12 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; } - case MOVEEND_CHANGED_ITEMS: // changed held items - for (i = 0; i < gBattlersCount; i++) - { - if (gBattleStruct->changedItems[i] != ITEM_NONE) - { - gBattleMons[i].item = gBattleStruct->changedItems[i]; - gBattleStruct->changedItems[i] = ITEM_NONE; - } - } - gBattleScripting.moveendState++; - break; case MOVEEND_ITEM_EFFECTS_TARGET: if (ItemBattleEffects(ITEMEFFECT_TARGET, gBattlerTarget, FALSE)) effect = TRUE; gBattleScripting.moveendState++; break; - case MOVEEND_MOVE_EFFECTS2: // For effects which should happen after target items, for example Knock Off after damage from Rocky Helmet. + case MOVEEND_FIRST_MOVE_BLOCK: // For effects which should happen after target items, for example Knock Off after damage from Rocky Helmet. { switch (gBattleStruct->moveEffect2) { @@ -6803,7 +6669,7 @@ static void Cmd_moveend(void) } gBattleScripting.moveendState++; break; - case MOVEEND_SUBSTITUTE: // update substitute + case MOVEEND_SUBSTITUTE: for (i = 0; i < gBattlersCount; i++) { if (gDisableStructs[i].substituteHP == 0) @@ -7062,6 +6928,126 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; } + case MOVEEND_DEFROST: + if (gBattleMons[gBattlerTarget].status1 & STATUS1_FREEZE + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(gBattlerTarget) + && gBattlerAttacker != gBattlerTarget + && (moveType == TYPE_FIRE || CanBurnHitThaw(gCurrentMove)) + && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) + { + gBattleMons[gBattlerTarget].status1 &= ~STATUS1_FREEZE; + BtlController_EmitSetMonData(gBattlerTarget, BUFFER_A, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1); + MarkBattlerForControllerExec(gBattlerTarget); + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_DefrostedViaFireMove; + effect = TRUE; + } + if (gBattleMons[gBattlerTarget].status1 & STATUS1_FROSTBITE + && IsBattlerAlive(gBattlerTarget) + && gBattlerAttacker != gBattlerTarget + && MoveThawsUser(originallyUsedMove) + && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) + { + gBattleMons[gBattlerTarget].status1 &= ~STATUS1_FROSTBITE; + BtlController_EmitSetMonData(gBattlerTarget, BUFFER_A, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1); + MarkBattlerForControllerExec(gBattlerTarget); + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_FrostbiteHealedViaFireMove; + effect = TRUE; + } + gBattleScripting.moveendState++; + break; + case MOVEEND_SECOND_MOVE_BLOCK: + if (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) + { + gBattleScripting.moveendState++; + break; + } + + switch (moveEffect) + { + case EFFECT_RECOIL_IF_MISS: + if (IsBattlerAlive(gBattlerAttacker) + && (!IsBattlerTurnDamaged(gBattlerTarget) || gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) + && !gBattleStruct->noTargetPresent) + { + if (B_RECOIL_IF_MISS_DMG >= GEN_5 || (B_CRASH_IF_TARGET_IMMUNE == GEN_4 && gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_DOESNT_AFFECT_FOE)) + gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerAttacker) / 2; + else if (B_RECOIL_IF_MISS_DMG == GEN_4 && (GetNonDynamaxMaxHP(gBattlerTarget) / 2) < gBattleStruct->moveDamage[gBattlerTarget]) + gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerTarget) / 2; + else // Fallback if B_RECOIL_IF_MISS_DMG is set to gen3 or lower. + gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerTarget) / 2; + + if (gBattleStruct->moveDamage[gBattlerAttacker] == 0) + gBattleStruct->moveDamage[gBattlerAttacker] = 1; + + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_RecoilIfMiss; + effect = TRUE; + } + break; + case EFFECT_RECOIL: + if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) + { + gBattleStruct->moveDamage[gBattlerAttacker] = max(1, gBattleStruct->moveDamage[gBattlerTarget] * max(1, GetMoveRecoil(gCurrentMove)) / 100); + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_MoveEffectRecoil; + effect = TRUE; + } + break; + case EFFECT_EXPLOSION: + if (!IsAbilityOnField(ABILITY_DAMP)) + { + gBattleStruct->moveDamage[gBattlerAttacker] = 0; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_FaintAttackerForExplosion; + effect = TRUE; + } + break; + case EFFECT_MAX_HP_50_RECOIL: + case EFFECT_MIND_BLOWN: + if (IsBattlerAlive(gBattlerAttacker) + && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FAILED) + && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD) + { + gBattleStruct->moveDamage[gBattlerAttacker] = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_MaxHp50Recoil; + effect = TRUE; + } + break; + case EFFECT_RAPID_SPIN: + if (IsBattlerTurnDamaged(gBattlerTarget)) + { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_RapidSpinAway; + effect = TRUE; + } + break; + case EFFECT_FELL_STINGER: + if (IsBattlerAlive(gBattlerAttacker) + && !IsBattlerAlive(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget) + && !NoAliveMonsForEitherParty() + && CompareStat(gBattlerAttacker, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + SET_STATCHANGER(STAT_ATK, GetGenConfig(GEN_CONFIG_FELL_STINGER_STAT_RAISE) >= GEN_7 ? 3 : 2, FALSE); + PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_ATK); + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_FellStingerRaisesStat; + effect = TRUE; + } + default: + break; + } + gBattleScripting.moveendState++; + break; + case MOVEEND_ITEM_EFFECTS_ATTACKER: + if (ItemBattleEffects(ITEMEFFECT_MOVE_END, gBattlerAttacker, FALSE)) + effect = TRUE; + gBattleScripting.moveendState++; + break; case MOVEEND_ABILITY_BLOCK: effect = HandleMoveEndAbilityBlock(gBattlerAttacker, gBattlerTarget, gCurrentMove); gBattleScripting.moveendState++; @@ -7316,9 +7302,6 @@ static void Cmd_moveend(void) if (!(emergencyExitBattlers & 1u << battler)) continue; - if (moveEffect == EFFECT_HIT_ESCAPE) - gBattlescriptCurrInstr = BattleScript_MoveEnd; // Prevent user switch-in selection - effect = TRUE; gBattleScripting.battler = battler; BattleScriptPushCursor(); @@ -7349,6 +7332,12 @@ static void Cmd_moveend(void) } gBattleScripting.moveendState++; break; + case MOVEEND_OPPORTUNIST: + if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0)) + effect = TRUE; // it loops through all battlers, so we increment after its done with all battlers + else + gBattleScripting.moveendState++; + break; case MOVEEND_SYMBIOSIS: for (i = 0; i < gBattlersCount; i++) { @@ -7376,6 +7365,17 @@ static void Cmd_moveend(void) gBattleStruct->sameMoveTurns[gBattlerAttacker]++; gBattleScripting.moveendState++; break; + case MOVEEND_CHANGED_ITEMS: + for (i = 0; i < gBattlersCount; i++) + { + if (gBattleStruct->changedItems[i] != ITEM_NONE) + { + gBattleMons[i].item = gBattleStruct->changedItems[i]; + gBattleStruct->changedItems[i] = ITEM_NONE; + } + } + gBattleScripting.moveendState++; + break; case MOVEEND_CLEAR_BITS: // Clear/Set bits for things like using a move for all targets and all hits. if (gSpecialStatuses[gBattlerAttacker].instructedChosenTarget) gBattleStruct->moveTarget[gBattlerAttacker] = gSpecialStatuses[gBattlerAttacker].instructedChosenTarget & 0x3; @@ -9587,20 +9587,6 @@ static void Cmd_useitemonopponent(void) gBattlescriptCurrInstr = cmd->nextInstr; } -static bool32 HasAttackerFaintedTarget(void) -{ - if (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) - && !IsBattleMoveStatus(gCurrentMove) - && (gLastHitBy[gBattlerTarget] == 0xFF || gLastHitBy[gBattlerTarget] == gBattlerAttacker) - && gBattleStruct->moveTarget[gBattlerAttacker] == gBattlerTarget - && gBattlerTarget != gBattlerAttacker - && gCurrentTurnActionNumber == GetBattlerTurnOrderNum(gBattlerAttacker) - && (gChosenMove == gChosenMoveByBattler[gBattlerAttacker] || gChosenMove == gBattleMons[gBattlerAttacker].moves[gChosenMovePos] || gChosenMove == GetMaxMove(gBattlerAttacker, gChosenMoveByBattler[gBattlerAttacker]))) - return TRUE; - else - return FALSE; -} - bool32 CanUseLastResort(u8 battler) { u32 i; @@ -10615,23 +10601,6 @@ static void Cmd_various(void) gBattleStruct->soulheartBattlerId = 0; break; } - case VARIOUS_TRY_ACTIVATE_FELL_STINGER: - { - VARIOUS_ARGS(); - if (GetMoveEffect(gCurrentMove) == EFFECT_FELL_STINGER - && IsBattlerAlive(gBattlerAttacker) - && HasAttackerFaintedTarget() - && !NoAliveMonsForEitherParty() - && CompareStat(gBattlerAttacker, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) - { - SET_STATCHANGER(STAT_ATK, (B_FELL_STINGER_STAT_RAISE >= GEN_7 ? 3 : 2), FALSE); - PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_ATK); - BattleScriptPush(cmd->nextInstr); - gBattlescriptCurrInstr = BattleScript_FellStingerRaisesStat; - return; - } - break; - } case VARIOUS_PLAY_MOVE_ANIMATION: { VARIOUS_ARGS(u16 move); diff --git a/test/battle/move_effect/fell_stinger.c b/test/battle/move_effect/fell_stinger.c new file mode 100644 index 0000000000..994cba3457 --- /dev/null +++ b/test/battle/move_effect/fell_stinger.c @@ -0,0 +1,43 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Fell Stinger raises user's Attack by 3 stages if it faints target") +{ + u32 config; + + PARAMETRIZE { config = GEN_7; } + PARAMETRIZE { config = GEN_6; } + + GIVEN { + WITH_CONFIG(GEN_CONFIG_FELL_STINGER_STAT_RAISE, config); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_FELL_STINGER); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FELL_STINGER); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FELL_STINGER, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + if (config == GEN_7) + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 3); + else + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Fell Stinger doesn't raise user's Attack if it doesn't faint target") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_FELL_STINGER); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FELL_STINGER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FELL_STINGER, player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +}