From 9c72392891e9ecf9f5daf5a596773abf23270cd0 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Wed, 12 Jun 2024 11:21:44 +0200 Subject: [PATCH] Fixes Shell Side Arm (#4753) * Fixes Shell Side Arm * Fixes to random call * hardcoded the effect to the move * minor change * minor change 2 * applied reviews --- asm/macros/battle_script.inc | 8 +- data/battle_scripts_1.s | 4 - include/battle.h | 1 + include/battle_scripts.h | 1 - include/battle_util.h | 3 +- include/constants/battle_move_effects.h | 1 - include/constants/battle_script_commands.h | 57 ++++++------ include/random.h | 1 + src/battle_ai_util.c | 5 +- src/battle_main.c | 8 +- src/battle_script_commands.c | 56 +++--------- src/battle_util.c | 88 +++++++++++++----- src/data/battle_move_effects.h | 6 -- src/data/moves_info.h | 2 +- test/battle/move_effect/shell_side_arm.c | 100 +++++++++++++++++++++ 15 files changed, 221 insertions(+), 120 deletions(-) create mode 100644 test/battle/move_effect/shell_side_arm.c diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 031c950465de..d20771619473 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1637,11 +1637,11 @@ .macro trygulpmissile callnative BS_TryGulpMissile .endm - + .macro tryactivategulpmissile callnative BS_TryActivateGulpMissile .endm - + .macro tryquash failInstr:req callnative BS_TryQuash .4byte \failInstr @@ -2211,10 +2211,6 @@ .4byte \failInstr .endm - .macro shellsidearmcheck - various BS_ATTACKER, VARIOUS_SHELL_SIDE_ARM_CHECK - .endm - .macro jumpifteanoberry jumpInstr:req various BS_ATTACKER, VARIOUS_TEATIME_TARGETS .4byte \jumpInstr diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index a103fc34a1cb..be649753900e 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -719,10 +719,6 @@ BattleScript_FlingMissed: ppreduce goto BattleScript_MoveMissedPause -BattleScript_EffectShellSideArm:: - shellsidearmcheck - goto BattleScript_EffectHit - BattleScript_EffectPhotonGeyser:: setphotongeysercategory goto BattleScript_EffectHit diff --git a/include/battle.h b/include/battle.h index 51d1a03bc3a9..ef4611869e4c 100644 --- a/include/battle.h +++ b/include/battle.h @@ -779,6 +779,7 @@ struct BattleStruct u8 supremeOverlordCounter[MAX_BATTLERS_COUNT]; u8 quickClawRandom[MAX_BATTLERS_COUNT]; u8 quickDrawRandom[MAX_BATTLERS_COUNT]; + u8 shellSideArmCategory[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT]; }; // The palaceFlags member of struct BattleStruct contains 1 flag per move to indicate which moves the AI should consider, diff --git a/include/battle_scripts.h b/include/battle_scripts.h index d7b58ba11a17..1fd77449f65d 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -796,7 +796,6 @@ extern const u8 BattleScript_EffectPlasmaFists[]; extern const u8 BattleScript_EffectHyperspaceFury[]; extern const u8 BattleScript_EffectAuraWheel[]; extern const u8 BattleScript_EffectPhotonGeyser[]; -extern const u8 BattleScript_EffectShellSideArm[]; extern const u8 BattleScript_EffectNoRetreat[]; extern const u8 BattleScript_EffectTarShot[]; extern const u8 BattleScript_EffectPoltergeist[]; diff --git a/include/battle_util.h b/include/battle_util.h index 6c4724c1d391..da47b3444ba6 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -155,7 +155,7 @@ u32 IsAbilityOnOpposingSide(u32 battler, u32 ability); u32 IsAbilityOnField(u32 ability); u32 IsAbilityOnFieldExcept(u32 battler, u32 ability); u32 IsAbilityPreventingEscape(u32 battler); -bool32 IsBattlerProtected(u32 battler, u32 move); +bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move); bool32 CanBattlerEscape(u32 battler); // no ability check void BattleScriptExecute(const u8 *BS_ptr); void BattleScriptPushCursorAndCallback(const u8 *BS_ptr); @@ -206,6 +206,7 @@ bool32 IsBelchPreventingMove(u32 battler, u32 move); bool32 HasEnoughHpToEatBerry(u32 battler, u32 hpFraction, u32 itemId); bool32 IsPartnerMonFromSameTrainer(u32 battler); u8 GetCategoryBasedOnStats(u32 battler); +void SetShellSideArmCategory(void); bool32 MoveIsAffectedBySheerForce(u32 move); bool32 TestIfSheerForceAffected(u32 battler, u16 move); void TryRestoreHeldItems(void); diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index b19bc0f7b1ba..13a300f064fd 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -302,7 +302,6 @@ enum { EFFECT_HYPERSPACE_FURY, EFFECT_AURA_WHEEL, EFFECT_PHOTON_GEYSER, - EFFECT_SHELL_SIDE_ARM, EFFECT_TERRAIN_PULSE, EFFECT_NO_RETREAT, EFFECT_TAR_SHOT, diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 7228954f214a..6fdbcc9e96f7 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -208,35 +208,34 @@ #define VARIOUS_JUMP_IF_WEATHER_AFFECTED 116 #define VARIOUS_JUMP_IF_LEAF_GUARD_PROTECTED 117 #define VARIOUS_SET_ATTACKER_STICKY_WEB_USER 118 -#define VARIOUS_SHELL_SIDE_ARM_CHECK 119 -#define VARIOUS_TRY_NO_RETREAT 120 -#define VARIOUS_TRY_TAR_SHOT 121 -#define VARIOUS_CAN_TAR_SHOT_WORK 122 -#define VARIOUS_CHECK_POLTERGEIST 123 -#define VARIOUS_CUT_1_3_HP_RAISE_STATS 124 -#define VARIOUS_TRY_END_NEUTRALIZING_GAS 125 -#define VARIOUS_JUMP_IF_UNDER_200 126 -#define VARIOUS_SET_SKY_DROP 127 -#define VARIOUS_CLEAR_SKY_DROP 128 -#define VARIOUS_SKY_DROP_YAWN 129 -#define VARIOUS_CURE_CERTAIN_STATUSES 130 -#define VARIOUS_TRY_RESET_NEGATIVE_STAT_STAGES 131 -#define VARIOUS_JUMP_IF_LAST_USED_ITEM_BERRY 132 -#define VARIOUS_JUMP_IF_LAST_USED_ITEM_HOLD_EFFECT 133 -#define VARIOUS_SAVE_BATTLER_ITEM 134 -#define VARIOUS_RESTORE_BATTLER_ITEM 135 -#define VARIOUS_BATTLER_ITEM_TO_LAST_USED_ITEM 136 -#define VARIOUS_SET_BEAK_BLAST 137 -#define VARIOUS_SWAP_SIDE_STATUSES 138 -#define VARIOUS_SWAP_STATS 139 -#define VARIOUS_TEATIME_INVUL 140 -#define VARIOUS_TEATIME_TARGETS 141 -#define VARIOUS_TRY_WIND_RIDER_POWER 142 -#define VARIOUS_ACTIVATE_WEATHER_CHANGE_ABILITIES 143 -#define VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES 144 -#define VARIOUS_STORE_HEALING_WISH 145 -#define VARIOUS_HIT_SWITCH_TARGET_FAILED 146 -#define VARIOUS_TRY_REVIVAL_BLESSING 147 +#define VARIOUS_TRY_NO_RETREAT 119 +#define VARIOUS_TRY_TAR_SHOT 120 +#define VARIOUS_CAN_TAR_SHOT_WORK 121 +#define VARIOUS_CHECK_POLTERGEIST 122 +#define VARIOUS_CUT_1_3_HP_RAISE_STATS 123 +#define VARIOUS_TRY_END_NEUTRALIZING_GAS 124 +#define VARIOUS_JUMP_IF_UNDER_200 125 +#define VARIOUS_SET_SKY_DROP 126 +#define VARIOUS_CLEAR_SKY_DROP 127 +#define VARIOUS_SKY_DROP_YAWN 128 +#define VARIOUS_CURE_CERTAIN_STATUSES 129 +#define VARIOUS_TRY_RESET_NEGATIVE_STAT_STAGES 130 +#define VARIOUS_JUMP_IF_LAST_USED_ITEM_BERRY 131 +#define VARIOUS_JUMP_IF_LAST_USED_ITEM_HOLD_EFFECT 132 +#define VARIOUS_SAVE_BATTLER_ITEM 133 +#define VARIOUS_RESTORE_BATTLER_ITEM 134 +#define VARIOUS_BATTLER_ITEM_TO_LAST_USED_ITEM 135 +#define VARIOUS_SET_BEAK_BLAST 136 +#define VARIOUS_SWAP_SIDE_STATUSES 137 +#define VARIOUS_SWAP_STATS 138 +#define VARIOUS_TEATIME_INVUL 139 +#define VARIOUS_TEATIME_TARGETS 140 +#define VARIOUS_TRY_WIND_RIDER_POWER 141 +#define VARIOUS_ACTIVATE_WEATHER_CHANGE_ABILITIES 142 +#define VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES 143 +#define VARIOUS_STORE_HEALING_WISH 144 +#define VARIOUS_HIT_SWITCH_TARGET_FAILED 145 +#define VARIOUS_TRY_REVIVAL_BLESSING 146 // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 0 diff --git a/include/random.h b/include/random.h index 37649cedf550..3f47dc1e16ea 100644 --- a/include/random.h +++ b/include/random.h @@ -189,6 +189,7 @@ enum RandomTag RNG_TRACE, RNG_FICKLE_BEAM, RNG_AI_ABILITY, + RNG_SHELL_SIDE_ARM, }; #define RandomWeighted(tag, ...) \ diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 27215563b9f0..dcf223070b0e 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -477,8 +477,9 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes } else if (gMovesInfo[move].effect == EFFECT_PHOTON_GEYSER) gBattleStruct->swapDamageCategory = (GetCategoryBasedOnStats(gBattlerAttacker) == DAMAGE_CATEGORY_PHYSICAL); - - if (gMovesInfo[move].effect == EFFECT_NATURE_POWER) + else if (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_SPECIAL) + gBattleStruct->swapDamageCategory = TRUE; + else if (gMovesInfo[move].effect == EFFECT_NATURE_POWER) move = GetNaturePowerMove(); gBattleStruct->dynamicMoveType = 0; diff --git a/src/battle_main.c b/src/battle_main.c index af073cafb6c5..fe36bde55211 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3677,8 +3677,8 @@ const u8* FaintClearSetData(u32 battler) gBattleStruct->zmove.toBeUsed[battler] = MOVE_NONE; gBattleStruct->zmove.effect = EFFECT_HIT; // Clear Dynamax data - UndoDynamax(battler); - + UndoDynamax(battler); + return result; } @@ -4162,6 +4162,7 @@ static void TryDoEventsBeforeFirstTurn(void) memset(gQueuedStatBoosts, 0, sizeof(gQueuedStatBoosts)); // erase all totem boosts just to be safe + SetShellSideArmCategory(); SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers if (gBattleTypeFlags & BATTLE_TYPE_ARENA) @@ -4254,6 +4255,7 @@ void BattleTurnPassed(void) *(&gBattleStruct->absentBattlerFlags) = gAbsentBattlerFlags; BattlePutTextOnWindow(gText_EmptyString3, B_WIN_MSG); + SetShellSideArmCategory(); SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers gBattleMainFunc = HandleTurnActionSelectionState; @@ -5835,7 +5837,7 @@ static void TryEvolvePokemon(void) sTriedEvolving |= gBitTable[i]; if (species == SPECIES_NONE && (gLeveledUpInBattle & gBitTable[i])) - { + { gLeveledUpInBattle &= ~(gBitTable[i]); species = GetEvolutionTargetSpecies(&gPlayerParty[i], EVO_MODE_BATTLE_ONLY, gLeveledUpInBattle, NULL); } diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index d2274562249a..8cef961abea8 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1479,7 +1479,7 @@ static void Cmd_attackcanceler(void) gBattlescriptCurrInstr = BattleScript_TookAttack; RecordAbilityBattle(gBattlerTarget, gLastUsedAbility); } - else if (IsBattlerProtected(gBattlerTarget, gCurrentMove) + else if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove) && (gCurrentMove != MOVE_CURSE || IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GHOST)) && (!gBattleMoveEffects[gMovesInfo[gCurrentMove].effect].twoTurnEffect || (gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS)) && gMovesInfo[gCurrentMove].effect != EFFECT_SUCKER_PUNCH @@ -1535,7 +1535,7 @@ static void Cmd_unused5(void) { CMD_ARGS(const u8 *failInstr); - if (IsBattlerProtected(gBattlerTarget, gCurrentMove)) + if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove)) { gMoveResultFlags |= MOVE_RESULT_MISSED; JumpIfMoveFailed(sizeof(*cmd), MOVE_NONE); @@ -1550,7 +1550,7 @@ static void Cmd_unused5(void) static bool8 JumpIfMoveAffectedByProtect(u16 move) { bool8 affected = FALSE; - if (IsBattlerProtected(gBattlerTarget, move)) + if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, move)) { gMoveResultFlags |= MOVE_RESULT_MISSED; JumpIfMoveFailed(7, move); @@ -2009,6 +2009,8 @@ static void Cmd_damagecalc(void) u8 moveType; GET_MOVE_TYPE(gCurrentMove, moveType); + if (gBattleStruct->shellSideArmCategory[gBattlerAttacker][gBattlerTarget] == DAMAGE_CATEGORY_SPECIAL && gCurrentMove == MOVE_SHELL_SIDE_ARM) + gBattleStruct->swapDamageCategory = TRUE; gBattleMoveDamage = CalculateMoveDamage(gCurrentMove, gBattlerAttacker, gBattlerTarget, moveType, 0, gIsCriticalHit, TRUE, TRUE); gBattlescriptCurrInstr = cmd->nextInstr; } @@ -2106,7 +2108,7 @@ static void Cmd_adjustdamage(void) gLastUsedItem = gBattleMons[gBattlerTarget].item; gSpecialStatuses[gBattlerTarget].focusBanded = FALSE; gSpecialStatuses[gBattlerTarget].focusSashed = FALSE; - + } else if (gSpecialStatuses[gBattlerTarget].sturdied) { @@ -3225,8 +3227,8 @@ void SetMoveEffect(bool32 primary, bool32 certain) { gBattleMons[gEffectBattler].status2 |= sStatusFlagsForMoveEffects[gBattleScripting.moveEffect]; gBattlescriptCurrInstr++; - } - else + } + else { gBattlescriptCurrInstr++; } @@ -6285,7 +6287,7 @@ static void Cmd_moveend(void) break; } } - + if (!(gBattleStruct->lastMoveFailed & gBitTable[gBattlerAttacker] || (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove && gBattleStruct->bouncedMoveIsUsed))) @@ -10487,44 +10489,6 @@ static void Cmd_various(void) gBattlescriptCurrInstr = cmd->nextInstr; return; } - case VARIOUS_SHELL_SIDE_ARM_CHECK: // 0% chance GameFreak actually checks this way according to DaWobblefet, but this is the only functional explanation at the moment - { - VARIOUS_ARGS(); - - u32 attackerAtkStat = gBattleMons[gBattlerAttacker].attack; - u32 targetDefStat = gBattleMons[gBattlerTarget].defense; - u32 attackerSpAtkStat = gBattleMons[gBattlerAttacker].spAttack; - u32 targetSpDefStat = gBattleMons[gBattlerTarget].spDefense; - u8 statStage; - u32 physical; - u32 special; - - gBattleStruct->swapDamageCategory = FALSE; - - statStage = gBattleMons[gBattlerAttacker].statStages[STAT_ATK]; - attackerAtkStat *= gStatStageRatios[statStage][0]; - attackerAtkStat /= gStatStageRatios[statStage][1]; - - statStage = gBattleMons[gBattlerTarget].statStages[STAT_DEF]; - targetDefStat *= gStatStageRatios[statStage][0]; - targetDefStat /= gStatStageRatios[statStage][1]; - - physical = ((((2 * gBattleMons[gBattlerAttacker].level / 5 + 2) * gMovesInfo[gCurrentMove].power * attackerAtkStat) / targetDefStat) / 50); - - statStage = gBattleMons[gBattlerAttacker].statStages[STAT_SPATK]; - attackerSpAtkStat *= gStatStageRatios[statStage][0]; - attackerSpAtkStat /= gStatStageRatios[statStage][1]; - - statStage = gBattleMons[gBattlerTarget].statStages[STAT_SPDEF]; - targetSpDefStat *= gStatStageRatios[statStage][0]; - targetSpDefStat /= gStatStageRatios[statStage][1]; - - special = ((((2 * gBattleMons[gBattlerAttacker].level / 5 + 2) * gMovesInfo[gCurrentMove].power * attackerSpAtkStat) / targetSpDefStat) / 50); - - if (((physical > special) || (physical == special && (Random() % 2) == 0))) - gBattleStruct->swapDamageCategory = TRUE; - break; - } case VARIOUS_JUMP_IF_LEAF_GUARD_PROTECTED: { VARIOUS_ARGS(const u8 *jumpInstr); @@ -13903,7 +13867,7 @@ static void Cmd_trymemento(void) if (B_MEMENTO_FAIL >= GEN_4 && (gBattleCommunication[MISS_TYPE] == B_MSG_PROTECTED || gStatuses3[gBattlerTarget] & STATUS3_SEMI_INVULNERABLE - || IsBattlerProtected(gBattlerTarget, gCurrentMove) + || IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove) || DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove))) { // Failed, target was protected. diff --git a/src/battle_util.c b/src/battle_util.c index 8a077ffb4e70..9202db6f35b0 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -8264,7 +8264,7 @@ bool32 IsMoveMakingContact(u32 move, u32 battlerAtk) if (!gMovesInfo[move].makesContact) { - if (gMovesInfo[move].effect == EFFECT_SHELL_SIDE_ARM && gBattleStruct->swapDamageCategory) + if (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][gBattlerTarget] == DAMAGE_CATEGORY_SPECIAL) return TRUE; else return FALSE; @@ -8280,59 +8280,59 @@ bool32 IsMoveMakingContact(u32 move, u32 battlerAtk) } } -bool32 IsBattlerProtected(u32 battler, u32 move) +bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) { // Decorate bypasses protect and detect, but not crafty shield if (move == MOVE_DECORATE) { - if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_CRAFTY_SHIELD) + if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_CRAFTY_SHIELD) return TRUE; - else if (gProtectStructs[battler].protected) + else if (gProtectStructs[battlerDef].protected) return FALSE; } // Z-Moves and Max Moves bypass protection (except Max Guard). if ((IsMaxMove(move) || gBattleStruct->zmove.active) - && (!gProtectStructs[battler].maxGuarded + && (!gProtectStructs[battlerDef].maxGuarded || gMovesInfo[move].argument == MAX_EFFECT_BYPASS_PROTECT)) return FALSE; // Max Guard is silly about the moves it blocks, including Teatime. - if (gProtectStructs[battler].maxGuarded && IsMoveBlockedByMaxGuard(move)) + if (gProtectStructs[battlerDef].maxGuarded && IsMoveBlockedByMaxGuard(move)) return TRUE; // Protective Pads doesn't stop Unseen Fist from bypassing Protect effects, so IsMoveMakingContact() isn't used here. // This means extra logic is needed to handle Shell Side Arm. if (GetBattlerAbility(gBattlerAttacker) == ABILITY_UNSEEN_FIST - && (gMovesInfo[move].makesContact || (gMovesInfo[move].effect == EFFECT_SHELL_SIDE_ARM && gBattleStruct->swapDamageCategory)) - && !gProtectStructs[battler].maxGuarded) // Max Guard cannot be bypassed by Unseen Fist + && (gMovesInfo[move].makesContact || (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_SPECIAL)) + && !gProtectStructs[battlerDef].maxGuarded) // Max Guard cannot be bypassed by Unseen Fist return FALSE; else if (gMovesInfo[move].ignoresProtect) return FALSE; - else if (gProtectStructs[battler].protected) + else if (gProtectStructs[battlerDef].protected) return TRUE; - else if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_WIDE_GUARD - && GetBattlerMoveTargetType(gBattlerAttacker, move) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY)) + else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_WIDE_GUARD + && GetBattlerMoveTargetType(gBattlerAttacker, move) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY)) return TRUE; - else if (gProtectStructs[battler].banefulBunkered) + else if (gProtectStructs[battlerDef].banefulBunkered) return TRUE; - else if (gProtectStructs[battler].burningBulwarked) + else if (gProtectStructs[battlerDef].burningBulwarked) return TRUE; - else if ((gProtectStructs[battler].obstructed || gProtectStructs[battler].silkTrapped) && !IS_MOVE_STATUS(move)) + else if ((gProtectStructs[battlerDef].obstructed || gProtectStructs[battlerDef].silkTrapped) && !IS_MOVE_STATUS(move)) return TRUE; - else if (gProtectStructs[battler].spikyShielded) + else if (gProtectStructs[battlerDef].spikyShielded) return TRUE; - else if (gProtectStructs[battler].kingsShielded && gMovesInfo[move].power != 0) + else if (gProtectStructs[battlerDef].kingsShielded && gMovesInfo[move].power != 0) return TRUE; - else if (gProtectStructs[battler].maxGuarded) + else if (gProtectStructs[battlerDef].maxGuarded) return TRUE; - else if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_QUICK_GUARD + else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_QUICK_GUARD && GetChosenMovePriority(gBattlerAttacker) > 0) return TRUE; - else if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_CRAFTY_SHIELD + else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_CRAFTY_SHIELD && IS_MOVE_STATUS(move)) return TRUE; - else if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_MAT_BLOCK + else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_MAT_BLOCK && !IS_MOVE_STATUS(move)) return TRUE; else @@ -11382,3 +11382,51 @@ void RemoveBattlerType(u32 battler, u8 type) *(u8 *)(&gBattleMons[battler].type1 + i) = TYPE_MYSTERY; } } + +void SetShellSideArmCategory(void) +{ + u32 battlerAtk, battlerDef; + u32 attackerAtkStat; + u32 targetDefStat; + u32 attackerSpAtkStat; + u32 targetSpDefStat; + u8 statStage; + u32 physical; + u32 special; + + for (battlerAtk = 0; battlerAtk < gBattlersCount; battlerAtk++) + { + attackerAtkStat = gBattleMons[battlerAtk].attack; + statStage = gBattleMons[battlerAtk].statStages[STAT_ATK]; + attackerAtkStat *= gStatStageRatios[statStage][0]; + attackerAtkStat /= gStatStageRatios[statStage][1]; + + attackerSpAtkStat = gBattleMons[battlerAtk].spAttack; + statStage = gBattleMons[battlerAtk].statStages[STAT_SPATK]; + attackerSpAtkStat *= gStatStageRatios[statStage][0]; + attackerSpAtkStat /= gStatStageRatios[statStage][1]; + + for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++) + { + if (battlerAtk == battlerDef) + continue; + + targetDefStat = gBattleMons[battlerDef].defense; + statStage = gBattleMons[battlerDef].statStages[STAT_DEF]; + targetDefStat *= gStatStageRatios[statStage][0]; + targetDefStat /= gStatStageRatios[statStage][1]; + + physical = ((((2 * gBattleMons[battlerAtk].level / 5 + 2) * gMovesInfo[MOVE_SHELL_SIDE_ARM].power * attackerAtkStat) / targetDefStat) / 50); + + targetSpDefStat = gBattleMons[battlerDef].spDefense; + statStage = gBattleMons[battlerDef].statStages[STAT_SPDEF]; + targetSpDefStat *= gStatStageRatios[statStage][0]; + targetSpDefStat /= gStatStageRatios[statStage][1]; + + special = ((((2 * gBattleMons[battlerAtk].level / 5 + 2) * gMovesInfo[MOVE_SHELL_SIDE_ARM].power * attackerSpAtkStat) / targetSpDefStat) / 50); + + if (((physical > special) || (physical == special && RandomPercentage(RNG_SHELL_SIDE_ARM, 50)))) + gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] = DAMAGE_CATEGORY_SPECIAL; + } + } +} diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index 9c53f50396ac..5a1536155384 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -1934,12 +1934,6 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = .battleTvScore = 0, // TODO: Assign points }, - [EFFECT_SHELL_SIDE_ARM] = - { - .battleScript = BattleScript_EffectShellSideArm, - .battleTvScore = 0, // TODO: Assign points - }, - [EFFECT_TERRAIN_PULSE] = { .battleScript = BattleScript_EffectHit, diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 35672537a6da..83513926133b 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -17350,7 +17350,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .description = COMPOUND_STRING( "Deals better of physical and\n" "special damage. May poison."), - .effect = EFFECT_SHELL_SIDE_ARM, + .effect = EFFECT_HIT, // The effect is hardcoded to the move since SetShellSideArmCategory() can't be used with anything but Shell Side Arm because of the BP requirement .power = 90, .type = TYPE_POISON, .accuracy = 100, diff --git a/test/battle/move_effect/shell_side_arm.c b/test/battle/move_effect/shell_side_arm.c new file mode 100644 index 000000000000..f0b3dd74dc22 --- /dev/null +++ b/test/battle/move_effect/shell_side_arm.c @@ -0,0 +1,100 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Shell Side Arm can be countered if it is physical") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SHELL_SIDE_ARM); } + OPPONENT(SPECIES_REGICE) { Defense(100); SpDefense(200); } + } WHEN { + TURN { MOVE(player, MOVE_SHELL_SIDE_ARM); MOVE(opponent, MOVE_COUNTER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COUNTER, opponent); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Shell Side Arm can be mirror coated if it is special") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SHELL_SIDE_ARM); } + OPPONENT(SPECIES_REGIROCK) { Defense(200); SpDefense(100); } + } WHEN { + TURN { MOVE(player, MOVE_SHELL_SIDE_ARM); MOVE(opponent, MOVE_MIRROR_COAT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, opponent); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Shell Side Arm does not change catogory mid-turn") +{ + s16 damage[3]; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SHELL_SIDE_ARM); } + OPPONENT(SPECIES_WOBBUFFET) { Defense(100); SpDefense(120); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SHELL_SIDE_ARM); } + TURN { MOVE(opponent, MOVE_LIGHT_SCREEN); MOVE(player, MOVE_SHELL_SIDE_ARM); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SHELL_SIDE_ARM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LIGHT_SCREEN, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player); + HP_BAR(opponent, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + EXPECT_EQ(damage[1], damage[2]); + } +} + +DOUBLE_BATTLE_TEST("Shell Side Arm is choosing it's type for each battler on the field") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); Moves(MOVE_SHELL_SIDE_ARM); } + PLAYER(SPECIES_WOBBUFFET) { Speed(20); } + OPPONENT(SPECIES_REGIROCK) { Speed(30); Defense(200); SpDefense(100); } + OPPONENT(SPECIES_REGICE) { Speed(30); Defense(100); SpDefense(200); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SHELL_SIDE_ARM, target: opponentRight); MOVE(opponentRight, MOVE_COUNTER); } + TURN { MOVE(playerLeft, MOVE_SHELL_SIDE_ARM, target: opponentLeft); MOVE(opponentLeft, MOVE_MIRROR_COAT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, playerLeft); + HP_BAR(opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COUNTER, opponentRight); + HP_BAR(playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, opponentLeft); + HP_BAR(playerLeft); + } +} + +DOUBLE_BATTLE_TEST("Shell Side Arm does not change category mid-turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); Moves(MOVE_SHELL_SIDE_ARM); } + PLAYER(SPECIES_WOBBUFFET) { Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(30); Defense(200); SpDefense(190); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(40); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SHELL_SIDE_ARM, target: opponentLeft); + MOVE(opponentRight, MOVE_LIGHT_SCREEN); + MOVE(opponentLeft, MOVE_MIRROR_COAT); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LIGHT_SCREEN, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, opponentLeft); + HP_BAR(playerLeft); + } +}