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
This commit is contained in:
Alex 2024-06-12 11:21:44 +02:00 committed by GitHub
parent 5ebdcdc9b0
commit 9c72392891
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 221 additions and 120 deletions

View file

@ -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

View file

@ -719,10 +719,6 @@ BattleScript_FlingMissed:
ppreduce
goto BattleScript_MoveMissedPause
BattleScript_EffectShellSideArm::
shellsidearmcheck
goto BattleScript_EffectHit
BattleScript_EffectPhotonGeyser::
setphotongeysercategory
goto BattleScript_EffectHit

View file

@ -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,

View file

@ -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[];

View file

@ -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);

View file

@ -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,

View file

@ -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

View file

@ -189,6 +189,7 @@ enum RandomTag
RNG_TRACE,
RNG_FICKLE_BEAM,
RNG_AI_ABILITY,
RNG_SHELL_SIDE_ARM,
};
#define RandomWeighted(tag, ...) \

View file

@ -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;

View file

@ -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);
}

View file

@ -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.

View file

@ -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;
}
}
}

View file

@ -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,

View file

@ -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,

View file

@ -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);
}
}